iOS底層-- RunLoop

手動(dòng)目錄

  • RunLoop 6大響應(yīng)事件
  • RunLoop 與線程的關(guān)系
  • RunLoop狀態(tài)監(jiān)聽
  • RunLoop 數(shù)據(jù)結(jié)構(gòu)
  • RunLoop流程
    • 如何進(jìn)行休眠的
  • RunLoop 與autoreleasePool
  • RunLoop 與GCD
  • RunLoop 應(yīng)用
    • 線程保活

什么是RunLoop令野?
RunLoop 是循環(huán)運(yùn)行铜幽,在程序運(yùn)行過(guò)程中玛歌,循環(huán)做一些事情。
RunLoop 是通過(guò)內(nèi)部的運(yùn)行循環(huán)搞乏,來(lái)對(duì)消息/事件進(jìn)行管理的一個(gè)對(duì)象季惯。

其作用是
1、保持程序的持續(xù)運(yùn)行
2蕴轨、處理APP中的各種事件(觸摸、定時(shí)器骇吭、performSelector)
3橙弱、節(jié)省cpu資源、提供程序的性能:該做事就做事燥狰,該休息就休息

RunLoop 6大響應(yīng)事件

  • block: __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • 調(diào)用Timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • 響應(yīng)source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • 響應(yīng)source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD主隊(duì)列__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • observer源:比如通知__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

當(dāng) RunLoop 進(jìn)行回調(diào)時(shí)棘脐,一般都是通過(guò)一個(gè)很長(zhǎng)的函數(shù)調(diào)用出去 (call out), 當(dāng)你在你的代碼中下斷點(diǎn)調(diào)試時(shí),通常能在調(diào)用棧上看到這些函數(shù)龙致。下面是這幾個(gè)函數(shù)的整理版本蛀缝,如果你在調(diào)用棧中看到這些長(zhǎng)函數(shù)名,在這里查找一下就能定位到具體的調(diào)用地點(diǎn)了:

可結(jié)合地步的【流程圖】來(lái)看

{
    /// 1. 通知Observers目代,即將進(jìn)入RunLoop
    /// 此處有Observer會(huì)創(chuàng)建AutoreleasePool: _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
    do {
 
        /// 2. 通知 Observers: 即將觸發(fā) Timer 回調(diào)屈梁。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
        /// 3. 通知 Observers: 即將觸發(fā) Source (非基于port的,Source0) 回調(diào)。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 4. 觸發(fā) Source0 (非基于port的) 回調(diào)榛了。
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 6. 通知Observers在讶,即將進(jìn)入休眠
        /// 此處有Observer釋放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
 
        /// 7. sleep to wait msg.
        mach_msg() -> mach_msg_trap();
        
 
        /// 8. 通知Observers,線程被喚醒
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
 
        /// 9. 如果是被Timer喚醒的霜大,回調(diào)Timer
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
 
        /// 9. 如果是被dispatch喚醒的构哺,執(zhí)行所有調(diào)用 dispatch_async 等方法放入main queue 的 block
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
 
        /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件喚醒了,處理這個(gè)事件
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
 
 
    } while (...);
 
    /// 10. 通知Observers战坤,即將退出RunLoop
    /// 此處有Observer釋放AutoreleasePool: _objc_autoreleasePoolPop();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}

RunLoop 與線程的關(guān)系

一一對(duì)應(yīng)的關(guān)系遮婶。

  • 在內(nèi)部,線程-runloop 在內(nèi)部以key-value的形式存儲(chǔ)在一個(gè)全局字典中 湖笨。
  • 當(dāng)然,子線程中中蹦骑,如果不主動(dòng)去獲取慈省,runloop是沒有的。主線程中的runloop是在main函數(shù)的 UIApplicationMain中創(chuàng)建的。

NSRunLoop 是對(duì) CFRunLoopRef 的包裝,
在代碼中边败,一般有這幾種寫法

    NSRunLoop *mainLoop1    = [NSRunLoop mainRunLoop];
    NSRunLoop *currentLoop1 = [NSRunLoop currentRunLoop];
    CFRunLoopRef mainLoop2  = CFRunLoopGetMain();
    CFRunLoopRef runloop2 = CFRunLoopGetCurrent();

底層都是C語(yǔ)言代碼袱衷,在追蹤源碼的時(shí)候,要用CFRunLoopGetMain 或者CFRunLoopGetCurrent來(lái)追蹤

我們?cè)?a target="_blank">源碼中能找到這樣的代碼:(源碼不是工程笑窜,創(chuàng)建一個(gè)空工程致燥,將源碼拖進(jìn)去)

// 追蹤 CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

static CFMutableDictionaryRef __CFRunLoops = NULL;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        
        
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
    // 進(jìn)行綁定 dict[@"pthread_main_thread_np"] = mainLoop
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

在源碼中 我們看到,所有的runloop存在一個(gè)字典CFMutableDictionaryRef,
字典中 以key-value 的形式存取排截,CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

類似于這樣
NSMutableDictionary *runloops = [NSMutableDictionary new];
runloops[@"祝線程"] = mainRunloop;
runloops[@"線程1"] = runloop1;
runloops[@"線程2"] = runloop2;

RunLoop狀態(tài)監(jiān)聽

RunLoop 有幾種狀態(tài)

enum CFRunLoopActivity {
    kCFRunLoopEntry              = (1 << 0),    // 即將進(jìn)入Loop   
    kCFRunLoopBeforeTimers      = (1 << 1),    // 即將處理 Timer        
    kCFRunLoopBeforeSources     = (1 << 2),    // 即將處理 Source  
    kCFRunLoopBeforeWaiting     = (1 << 5),    // 即將進(jìn)入休眠     
    kCFRunLoopAfterWaiting      = (1 << 6),    // 剛從休眠中喚醒   
    kCFRunLoopExit               = (1 << 7),    // 即將退出Loop  
    kCFRunLoopAllActivities     = 0x0FFFFFFFU  // 包含上面所有狀態(tài)  
};

RunLoop的狀態(tài)是可以添加監(jiān)聽來(lái)看看是怎樣的狀態(tài)

// 方法1:   指定 執(zhí)行方法的 方式
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
       ......
        default:
            break;
    }
}

{
//     創(chuàng)建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}


// 方法2:  block 方式
{
    // 創(chuàng)建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
                
            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
    });
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放
    CFRelease(observer);
}

RunLoop 數(shù)據(jù)結(jié)構(gòu)

追蹤源碼 CFRunLoop

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
}; 

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

__CFRunLoop 關(guān)鍵5個(gè)信息

  • _pthread:
    與之對(duì)應(yīng)的線程
  • _commonModes:
  • _commonModeItems:
    在commonModes狀態(tài)下運(yùn)行的對(duì)象(例如Timer)
  • _currentMode:
    在當(dāng)前l(fā)oop下運(yùn)行的mode(即使有多種mode嫌蚤,但同一時(shí)刻只能在一個(gè)mode下運(yùn)行)
  • modes:
    運(yùn)行的所有模式----- 類似于CFMutableSetRef< CFRunLoopModeRef >

__CFRunLoopMode 關(guān)鍵的信息

  • _sources0
    觸摸事件處理
    performSelector:onThread:withObject:waitUntilDone:
  • _sources1
    基于Port的線程間的通訊
    系統(tǒng)事件捕捉(比如點(diǎn)擊事件,由source1捕捉断傲,然后包裝之后交給source0處理)
  • _observers
    監(jiān)聽RunLoop的狀態(tài)
    UI刷新(在BeforeWaiting的時(shí)候刷新)
    AutoreleasePool
  • _timers
    NSTimer
    performSelector:withObject:脱吱、 afterDelay:

RunLoopMode 類型

  • NSDefaultRunLoopMode
    App的默認(rèn)Mode
  • UITrackingRunLoopMode
    滾動(dòng)的時(shí)候處于這個(gè)mode
  • NSRunLoopCommonModes
    這是一個(gè)占位用的Mode,不是一種真正的Mode认罩,它是上面2個(gè)mode的集合
  • UIInitializationRunLoopMode
    在剛啟動(dòng)App時(shí)進(jìn)入的第一個(gè)Mode箱蝠,啟動(dòng)完成后就不再使用
  • GSEventReceiveRunLoopMode
    接受系統(tǒng)事件的Mode,通常由系統(tǒng)去使用

前三種mode 對(duì)外提供垦垂,后面的2中不能使用宦搬。

RunLoop 對(duì)應(yīng)關(guān)系
RunLoop結(jié)構(gòu)

為什么 一個(gè)runloop里包含多個(gè)mode?
不同mode中的 source0劫拗、source1间校、timer、observe 能分割開來(lái)杨幼,互不影響撇簿。

RunLoop流程

流程問(wèn)題,從run開始
(內(nèi)部代碼太多差购,貼一份精簡(jiǎn)的代碼)

// CFRunLoopRun
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    /// 首先根據(jù)modeName找到對(duì)應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    /// 通知 Observers: RunLoop 即將進(jìn)入 loop四瘫。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 內(nèi)部函數(shù),進(jìn)入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 通知 Observers: RunLoop 即將退出欲逃。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}


static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    do {
        
        /// 通知 Observers: 即將處理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// 通知 Observers: 即將處理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        /// 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /// 處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// 處理sources0返回為YES
        if (sourceHandledThisLoop) {
            /// 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        /// 判斷有無(wú)端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 處理消息
            goto handle_msg;
        }
        
        /// 通知 Observers: 即將進(jìn)入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 等待被喚醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// 通知 Observers: 被喚醒找蜜,結(jié)束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if (被Timer喚醒) {
            /// 處理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (被GCD喚醒) {
            /// 處理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if (被Source1喚醒) {
            /// 被Source1喚醒稳析,處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        /// 處理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        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;
        }
        
    } while (0 == retVal);
    
    return retVal;
}
  • 1洗做、observers 進(jìn)入loop(entry)

  • 2、通知observer 即將處理Timer(BeforeTimers)

  • 3彰居、通知observer即將處理Sources (BeforeSources )

  • 4诚纸、處理GCD

  • 5、處理source0

    • 5.1 如果source0 有GCD需要處理陈惰,就處理GCD
  • 6畦徘、判斷有沒有端口消息(source1),有的話 繼續(xù)處理

  • 7、通知 observe即將進(jìn)入休眠(BeforeWaiting)井辆,進(jìn)入休眠之后進(jìn)行事件監(jiān)聽关筒。(會(huì)一直卡在這直到被其他喚醒)

  • 8、通知observe結(jié)束休眠(AfterWaiting)

    • 8.1杯缺、Timer喚醒 : 處理Timer
    • 8.2蒸播、GCD喚醒:處理GCD
    • 8.3、source1喚醒:處理Source1
  • 9萍肆、執(zhí)行GCD

  • 10袍榆、根據(jù)之前的執(zhí)行結(jié)果 決定loop是繼續(xù)循環(huán) 還是退出

  • 11、通知observe 退出(exit )匾鸥。

流程圖

這里有一個(gè)點(diǎn)需要注意 :
能夠喚醒loop的有3個(gè):Timer蜡塌、GCDSource1勿负;
Source0不能直接喚醒loop
source0是用戶觸摸事件馏艾,實(shí)際上是有系統(tǒng)(Source1)捕捉到點(diǎn)擊事件,然后包裝之后交給source0處理

如何進(jìn)行休眠的

在用戶層奴愉,是無(wú)法達(dá)到休眠(等待而不執(zhí)行任何事物)的目的琅摩。通過(guò)內(nèi)核去操作。

以下內(nèi)容摘錄于:深入理解RunLoop
mach_msg() 函數(shù)實(shí)際上是調(diào)用了一個(gè) Mach 陷阱 (trap)锭硼,即函數(shù)mach_msg_trap()房资,陷阱這個(gè)概念在 Mach 中等同于系統(tǒng)調(diào)用。當(dāng)你在用戶態(tài)調(diào)用 mach_msg_trap() 時(shí)會(huì)觸發(fā)陷阱機(jī)制檀头,切換到內(nèi)核態(tài)轰异;內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的 mach_msg() 函數(shù)會(huì)完成實(shí)際的工作,如下圖:

休眠的實(shí)現(xiàn)

RunLoop 與autoreleasePool

  • 即將進(jìn)入Loop(kCFRunLoopEntry)暑始,其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池搭独。其order是-2147483647,優(yōu)先級(jí)最高廊镜,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前牙肝。
  • 準(zhǔn)備進(jìn)入休眠(kCFRunLoopBeforeWaiting),此時(shí)調(diào)用 _objc_autoreleasePoolPop() 對(duì)釋放池里面的 對(duì)象進(jìn)行release操作
  • 即將退出Loop(kCFRunLoopExit)此時(shí)調(diào)用 _objc_autoreleasePoolPop()釋放自動(dòng)釋放池嗤朴。這個(gè) Observer的order是2147483647配椭,確保池子釋放在所有回調(diào)之后。

RunLoop 與GCD

RunLoop與GCD的關(guān)系與2點(diǎn):
1雹姊、在do..while 循環(huán)中股缸,判斷runloop有沒有超時(shí),用的是GCD的source_timer
2吱雏、當(dāng)回主線程敦姻,也就是 GCD使用main_queue的時(shí)候寺酪,會(huì)向runloop發(fā)送一個(gè)_MAIN_DISPATCH_QUEUE的消息來(lái)喚醒runloop。這也是為什么子線程不能刷UI的原因之一替劈。

  • 子線程 的GCD不能做UI的刷新等操作。是因?yàn)樵谧泳€程中得滤,無(wú)法喚醒runloop進(jìn)行UI刷新陨献。
    我們做以下 操作:
    - (void)task8 {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          self.view.backgroundColor = [UIColor redColor];
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              NSLog(@"1");
          });
      });
    
    // [[NSRunLoop currentRunLoop] run];
    }
    
    執(zhí)行這段代碼,雖然設(shè)置backgroundColor的時(shí)候懂更,顏色沒有發(fā)生變化眨业,但是當(dāng)dispatch_after執(zhí)行,或者主動(dòng)執(zhí)行runloop run 操作 的時(shí)候沮协,顏色成功的改變了龄捡。

RunLoop 應(yīng)用

線程保活

上面說(shuō)了runloop的原理慷暂。
那么就知道為什么有時(shí)候NSTimer不起效了聘殖。

RunLoop還能干什么? --線程毙腥穑活
一般來(lái)說(shuō)奸腺,我們?cè)谧泳€程里面執(zhí)行完任務(wù),子線程就會(huì)退出血久,但是突照,有時(shí)候我們需要在子線程多次執(zhí)行任務(wù),就需要線程一直不退出來(lái)節(jié)省消耗氧吐。
我們可以這樣做讹蘑。
------向子線程中 添加一個(gè)port (相當(dāng)于向runloop中 添加了一個(gè) source1)保證runloop中一直有事件要處理。
代碼如下:

@property (nonatomic, strong) JEThread *thread;
@property (nonatomic, assign) BOOL stopped;

{
  __weak typeof(self) weakSelf = self;
    _thread = [[JEThread alloc] initWithBlock:^{         // iOS 10.0
        [[NSRunLoop currentRunLoop ] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
//            [[NSRunLoop currentRunLoop] run];         // 這個(gè)需要注意筑舅,內(nèi)部會(huì)無(wú)限循環(huán)run座慰, 不能手動(dòng)停止
            while (weakSelf && !weakSelf.stopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        NSLog(@"-------end-------");
    }];
    [self.thread start];
}

- (void)stop {
    if (!self.thread)  return;
    // 在子線程調(diào)用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

// 用于停止子線程的RunLoop
- (void)stopThread
{
    // 設(shè)置標(biāo)記為NO
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);

    self.thread = nil;
}

- (void)dealloc {
    [self stop];
    NSLog(@"vc dealloc");
}


// 在子線程里處理自己的事情
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (!self.thread)  return;
    [self performSelector:@selector(doSomeThings) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)doSomeThings {
    NSLog(@"1");
}

這里面有幾個(gè)點(diǎn)需要注意:

  • 1、為什么沒有用[runloop run] 而是用runMode:beforeDate?
    因?yàn)閞un 內(nèi)部進(jìn)行循環(huán)調(diào)用 runMode:beforeDate豁翎,我們需要自己打破這個(gè)循環(huán)角骤,所以我們要進(jìn)行自己的 while 條件循環(huán)。

  • 2心剥、stop方法里面 的 waitUntilDone 必須為YES邦尊?
    考慮到可能會(huì)在dealloc里面調(diào)用stop,如果不等待优烧,就會(huì)先釋放了self蝉揍,這個(gè)時(shí)候再進(jìn)行 self.stopped 就是無(wú)效的。

參考 文章:
深入理解RunLoop ????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畦娄,一起剝皮案震驚了整個(gè)濱河市电禀,隨后出現(xiàn)的幾起案子蜈垮,更是在濱河造成了極大的恐慌膀钠,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件励饵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡滑燃,警方通過(guò)查閱死者的電腦和手機(jī)役听,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)表窘,“玉大人典予,你說(shuō)我怎么就攤上這事±盅希” “怎么了瘤袖?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)昂验。 經(jīng)常有香客問(wèn)我捂敌,道長(zhǎng),這世上最難降的妖魔是什么凛篙? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任黍匾,我火速辦了婚禮,結(jié)果婚禮上呛梆,老公的妹妹穿的比我還像新娘锐涯。我一直安慰自己,他們只是感情好填物,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布纹腌。 她就那樣靜靜地躺著,像睡著了一般滞磺。 火紅的嫁衣襯著肌膚如雪升薯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天击困,我揣著相機(jī)與錄音涎劈,去河邊找鬼。 笑死阅茶,一個(gè)胖子當(dāng)著我的面吹牛蛛枚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脸哀,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蹦浦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了撞蜂?” 一聲冷哼從身側(cè)響起盲镶,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤侥袜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后溉贿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枫吧,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年宇色,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了由蘑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡代兵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爷狈,到底是詐尸還是另有隱情植影,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布涎永,位于F島的核電站思币,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏羡微。R本人自食惡果不足惜谷饿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妈倔。 院中可真熱鬧博投,春花似錦、人聲如沸盯蝴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捧挺。三九已至虑绵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闽烙,已是汗流浹背翅睛。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黑竞,地道東北人捕发。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像摊溶,于是被迫代替她去往敵國(guó)和親爬骤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350