你要知道的iOS多線程NSThread叠殷、GCD堕伪、NSOperation揖庄、RunLoop都在這里
轉載請注明出處 http://www.reibang.com/p/f0a7fb39f79c
本系列文章主要講解iOS中多線程的使用,包括:NSThread欠雌、GCD蹄梢、NSOperation以及RunLoop的使用方法詳解,本系列文章不涉及基礎的線程/進程富俄、同步/異步禁炒、阻塞/非阻塞、串行/并行霍比,這些基礎概念幕袱,有不明白的讀者還請自行查閱。本系列文章將分以下幾篇文章進行講解悠瞬,讀者可按需查閱们豌。
- iOS多線程——你要知道的NSThread都在這里
- iOS多線程——你要知道的GCD都在這里
- iOS多線程——你要知道的NSOperation都在這里
- iOS多線程——你要知道的RunLoop都在這里
- iOS多線程——RunLoop與GCD、AutoreleasePool
RunLoop的執(zhí)行者 __CFRunLoopRun源碼解析
在前一篇文章中由于篇幅問題沒有具體分析__CFRunLoopRun
函數的源碼浅妆,這一部分先來看一下真正執(zhí)行RunLoop
循環(huán)的源碼:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//獲取一個CPU滴答數的時間望迎,精度在納秒級
uint64_t startTSR = mach_absolute_time();
//判斷RunLoop是否被停止
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
/*
如果當前RunLoop是主線程關聯的RunLoop
dispatchPort被置為GCD主隊列的端口號
*/
mach_port_name_t dispatchPort = MACH_PORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//使用GCD的dispatch_source_t實現RunLoop的超時機制
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
//do-while循環(huán)的判斷條件,retVal為0則保持循環(huán)
int32_t retVal = 0;
do {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
//調用__CFRunLoopDoObservers函數凌外,通知監(jiān)聽器即將處理Timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//調用__CFRunLoopDoObservers函數辩尊,通知監(jiān)聽器即將處理Source
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
/*
執(zhí)行source0(非基于端口)事件
sourceHandledThisLoop在執(zhí)行了source0事件后置為true在一個循環(huán)結束后會使用
__CFRunLoopRun函數有一個形參stopAfterHandle,如果其為true
并且sourceHandledThisLoop也為true在一個循環(huán)結束后就會退出RunLoop
*/
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//執(zhí)行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
/*
判斷是否有source1(基于端口的)事件需要處理
如果有則跳轉到handle_msg標簽處執(zhí)行
*/
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//獲取到內核的消息
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
//調用__CFRunLoopDoObservers函數通知監(jiān)聽器趴乡,RunLoop即將進入睡眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//休眠的循環(huán)对省,等到source1事件、Timer時間到達晾捏、外部喚醒
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//前述代碼中如果有source1事件則跳轉到該標簽進行處理蒿涎,喚醒后也會走這里,處理Timer和source1事件
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
//在windows平臺下的條件編譯的代碼
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
//沒有source1事件需要處理
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
}
//被顯示喚醒
else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//Timer時間到達需要進行處理
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
//同上處理Timer
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
/*
處理主線程的主隊列端口事件
比如使用dispatch_async(dispatch_get_main_queue(),^{
});
函數惦辛,調用主隊列來執(zhí)行任務劳秋,這個任務就在這里處理
*/
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
//真正執(zhí)行上述任務的函數
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
//處理source1事件
else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
//執(zhí)行添加進來的block
__CFRunLoopDoBlocks(rl, rlm);
//一個循環(huán)后判斷執(zhí)行的結果條件
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
由于作者水平有限,有些部分的代碼也沒看太懂,只能注釋到這里玻淑,但總體的執(zhí)行流程都和前一篇文章中講解的一樣嗽冒,從這個源碼中也能看到不少我們需要掌握的部分。
RunLoop與GCD
從上面的源碼中可以看到补履,在執(zhí)行RunLoop
的循環(huán)中使用了GCD
的dispatch_source_t
來實現其超時機制添坊。
還有一個比較重要的地方就是GCD
中將任務提交到主線程的主隊列即dispatch_get_main_queue()
時,這里的任務是由RunLoop
負責執(zhí)行箫锤,從源碼中可以看到贬蛙,如果當前RunLoop
對象是主線程關聯的,則會執(zhí)行下述代碼:
dispatchPort = _dispatch_get_main_queue_port_4CF();
這行代碼獲取了主線程主隊列的端口號并賦值谚攒,接著在handle_msg
標簽后的代碼會判斷主隊列中是否有任務需要執(zhí)行阳准,如果有,則調用__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
函數進行處理馏臭。但只有主隊列的任務會交由RunLoop
對象處理野蝇,其他隊列的則由GCD
自行處理。
RunLoop與AutoreleasePool
關于AutoreleasePool
的原理不是本文的重點括儒,有興趣的讀者推薦看以下兩篇文章:
黑幕背后的Autorelease http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
自動釋放池的前世今生 ---- 深入解析 Autoreleasepool http://www.reibang.com/p/32265cbb2a26
當程序運行后绕沈,輸出[NSRunLoop mainRunLoop]
,然后查看observers
部分的輸出塑崖,可以看到有如下輸出:
"<CFRunLoopObserver 0x1c412db60 [0x1b220f240]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = <_wrapRunLoopWithAutoreleasePoolHandler> (0x18ad71908), context = <CFArray 0x1c4454970 [0x1b220f240]>{type = mutable-small, count = 1, values = (\n\t0 : <0x102bf8048>\n)}}",
"<CFRunLoopObserver 0x1c412dac0 [0x1b220f240]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = <_wrapRunLoopWithAutoreleasePoolHandler> (0x18ad71908), context = <CFArray 0x1c4454970 [0x1b220f240]>{type = mutable-small, count = 1, values = (\n\t0 : <0x102bf8048>\n)}}"
首先查看activities
屬性七冲,這個值就是前一篇文章講解的監(jiān)聽器監(jiān)聽事件的枚舉值,具體值如下:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
其中0x1
即kCFRunLoopEntry
规婆,監(jiān)聽RunLoop
對象進入循環(huán)的事件澜躺,0xa0
即kCFRunLoopBeforeWaiting | kCFRunLoopExit
,監(jiān)聽RunLoop
即將進入休眠和RunLoop
對象退出循環(huán)的事件抒蚜。
程序運行后產生的兩個CFRunLoopObserver
一個監(jiān)聽RunLoop
對象進入循環(huán)的事件掘鄙,執(zhí)行回調函數_wrapRunLoopWithAutoreleasePoolHandler
,并且優(yōu)先級order
為-2147483647
即32位整數的最小值嗡髓,保證了它的優(yōu)先級最高操漠。在回調內會調用_objc_autoreleasePoolPush
函數來創(chuàng)建AutoreleasePool
,由于它的優(yōu)先級最高饿这,所以能夠保證自動釋放池在其他回調執(zhí)行前得到創(chuàng)建浊伙。
另一個監(jiān)聽器監(jiān)聽RunLoop
對象進入休眠和退出循環(huán)的事件,回調函數同樣是_wrapRunLoopWithAutoreleasePoolHandler
长捧,而優(yōu)先級為2147483647
即32位整數的最大值嚣鄙,保證它的優(yōu)先級最低。對于監(jiān)聽進入休眠狀態(tài)時回調函數內首先會調用_objc_autoreleasePoolPop
函數來釋放AutoreleasePool
然后使用_objc_autoreleasePoolPush
函數重新創(chuàng)建一個自動釋放池串结。優(yōu)先級最低保證了釋放操作是在其他所有回調執(zhí)行之后發(fā)生哑子。
main
函數就是被@autoreleasepool
包圍著舅列,所以在主線程中創(chuàng)建的任何對象都會及時被釋放。
通過上面的講解卧蜓,不難發(fā)現帐要,需要進入到runloop
中才會釋放舊的自動釋放池然后創(chuàng)建新的自動釋放池,那如果程序在處理一個比較耗時且占用內存較大的任務時弥奸,在沒有任何事件產生的情況下是不會進入到runloop
中榨惠,那些本應該立即釋放的局部變量就不會被釋放,程序就會被占用過多的內存盛霎,如下栗子:
- (void)btnClickedHandler
{
NSArray *urls = ;
for (NSURL *url in urls) {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
}
加上之前的監(jiān)聽runloop
狀態(tài)的代碼冒冬,可以發(fā)現,在for循環(huán)結束前都不會改變runloop
的狀態(tài)摩渺,runloop
一直處于休眠的狀態(tài),所以for循環(huán)大量創(chuàng)建的局部變量不會得到釋放剂邮,就會占用過多的內存摇幻,直到runloop
改變狀態(tài),此時就需要手動添加@autoreleasepool
來手動創(chuàng)建和釋放自動釋放池:
- (void)btnClickedHandler
{
NSArray *urls = ;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
}
}
上面的栗子來源于官方文檔挥萌,具體運行結果绰姻,讀者可自行實驗,并在xcode
中觀察內存占用情況引瀑。
RunLoop實現常駐內存的線程
直接看代碼:
+ (void)entryPoint
{
//設置當前線程名為MyThread
[[NSThread currentThread] setName:@"MyThread"];
//獲取NSRunLoop對象狂芋,第一次獲取不存在時系統(tǒng)會創(chuàng)建一個
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
/*
添加一個Source1事件的監(jiān)聽端口
RunLoop對象會一直監(jiān)聽這個端口,由于這個端口不會有任何事件到來所以不會產生影響
監(jiān)聽模式是默認模式憨栽,可以修改為Common
*/
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//啟動RunLoop
[runloop run];
}
+ (NSThread *)longTermThread
{
//靜態(tài)變量保存常駐內存的線程對象
static NSThread *longTermThread = nil;
//使用GCD dispatch_once 在應用生命周期只執(zhí)行一次常駐線程的創(chuàng)建工作
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//創(chuàng)建一個線程對象帜矾,并執(zhí)行entryPoint方法
longTermThread = [[NSThread alloc] initWithTarget:self selector:@selector(entryPoint) object:nil];
//啟動線程,啟動后就會執(zhí)行entryPoint方法
[longTermThread start];
});
return longTermThread;
}
- (void)viewDidLoad
{
//獲取這個常駐內存的線程
NSThread *thread = [ViewController longTermThread];
//在該線程上提交任務
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
}
上面的栗子很好理解屑柔,主要利用了一個source1
事件的監(jiān)聽屡萤,由于Mode
的Source/Observer/Timer
中的Observer
不為空,所以RunLoop
不會退出循環(huán)掸宛,能夠常駐內存死陆。
備注
由于作者水平有限,難免出現紕漏唧瘾,如有問題還請不吝賜教措译。