iOS多線程——RunLoop與GCD、AutoreleasePool

你要知道的iOS多線程NSThread叠殷、GCD堕伪、NSOperation揖庄、RunLoop都在這里

轉載請注明出處 http://www.reibang.com/p/f0a7fb39f79c

本系列文章主要講解iOS中多線程的使用,包括:NSThread欠雌、GCD蹄梢、NSOperation以及RunLoop的使用方法詳解,本系列文章不涉及基礎的線程/進程富俄、同步/異步禁炒、阻塞/非阻塞、串行/并行霍比,這些基礎概念幕袱,有不明白的讀者還請自行查閱。本系列文章將分以下幾篇文章進行講解悠瞬,讀者可按需查閱们豌。

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)中使用了GCDdispatch_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
};

其中0x1kCFRunLoopEntry规婆,監(jiān)聽RunLoop對象進入循環(huán)的事件澜躺,0xa0kCFRunLoopBeforeWaiting | 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)聽屡萤,由于ModeSource/Observer/Timer中的Observer不為空,所以RunLoop不會退出循環(huán)掸宛,能夠常駐內存死陆。

備注

由于作者水平有限,難免出現紕漏唧瘾,如有問題還請不吝賜教措译。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饰序,隨后出現的幾起案子领虹,更是在濱河造成了極大的恐慌,老刑警劉巖菌羽,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掠械,死亡現場離奇詭異由缆,居然都是意外死亡,警方通過查閱死者的電腦和手機猾蒂,發(fā)現死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門均唉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肚菠,你說我怎么就攤上這事舔箭。” “怎么了蚊逢?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵层扶,是天一觀的道長。 經常有香客問我烙荷,道長镜会,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任终抽,我火速辦了婚禮戳表,結果婚禮上,老公的妹妹穿的比我還像新娘昼伴。我一直安慰自己匾旭,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布圃郊。 她就那樣靜靜地躺著价涝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪持舆。 梳的紋絲不亂的頭發(fā)上色瘩,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音逸寓,去河邊找鬼泞遗。 笑死,一個胖子當著我的面吹牛席覆,可吹牛的內容都是我干的史辙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼佩伤,長吁一口氣:“原來是場噩夢啊……” “哼聊倔!你這毒婦竟也來了?” 一聲冷哼從身側響起生巡,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤耙蔑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后孤荣,有當地人在樹林里發(fā)現了一具尸體甸陌,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡须揣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了钱豁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耻卡。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牲尺,靈堂內的尸體忽然破棺而出卵酪,到底是詐尸還是另有隱情,我是刑警寧澤谤碳,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布溃卡,位于F島的核電站,受9級特大地震影響蜒简,放射性物質發(fā)生泄漏瘸羡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一搓茬、第九天 我趴在偏房一處隱蔽的房頂上張望最铁。 院中可真熱鬧,春花似錦垮兑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至磕谅,卻和暖如春私爷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背膊夹。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工衬浑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人放刨。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓工秩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親进统。 傳聞我的和親對象是個殘疾皇子助币,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容