RunLoop

runloop 的狀態(tài)待完善

相關(guān)面試題

  • 講講 RunLoop,項目中有用到嗎?
    1、控制線程的生命周期(線程?;?
    2、解決 NSTimer 在滑動時停止工作的問題
    3、監(jiān)控應(yīng)用卡幀
    4 、性能優(yōu)化
  • runloop內(nèi)部實現(xiàn)邏輯?
  • runloop和線程的關(guān)系?
    線程與 runloop 一一對應(yīng),主線程的 runloop 默認開啟,子線程的 runloop 在第一次獲取的時候開啟,runloop 在線程結(jié)束時銷毀,線程和 runloop 存放在一個全局的字典中,該字典以線程 key,runloop 作為 value.
  • timer 與 runloop 的關(guān)系?
    timer 需要添加到 runloop 中才能工作,runloop 中有一個CFRunLoopTimerRef 類型的事件源專門用來出來 timer 事件.
  • 程序中添加每3秒響應(yīng)一次的NSTimer,當拖動tableview時timer可能無法響應(yīng)要怎么解決?
    將 timer 添加到 runloop 的 commonModel 模式下
  • runloop 是怎么響應(yīng)用戶操作的, 具體流程是什么樣的?
  • 說說runLoop的幾種狀態(tài)
  • runloop的mode作用是什么?

什么是 RunLoop

  • RunLoop 顧名思義就是在運行過程中循環(huán)做一些事情
  • 應(yīng)用范疇:
    1、定時器(Timer)、PerformSelector
    2、GCD Async Main Queue
    3、事件響應(yīng)、手勢識別、界面刷新
    4、網(wǎng)絡(luò)請求
    5、AutoreleasePool

RunLoop的作用

1、保持程序的持續(xù)運行
2、處理App中的各種事件(比如觸摸事件、定時器事件等)
3、節(jié)省CPU資源,提高程序性能:該做事時做事,該休息時休息

  • 如果沒有 RunLoop 程序執(zhí)行完就會退出,例如下面的代碼,程序在執(zhí)行完main 函數(shù)后就會退出。
int main (int argc, const char * argv[]){
  @autoreleasepool{
    NSLog(@"hello world");
  }
return 0;
}
  • 但是我們通??吹降膇os 程序運行在手機上后就一直處在運行狀態(tài)等待用戶的響應(yīng),它是怎么做到的呢?其實是因為ios 應(yīng)用程序在 UIApplicationMain函數(shù)中幫我們開啟了一個 runloop,runloop 保持我們的 ios 程序持續(xù)運行,下面是一個 ios 的 main 函數(shù):
int main(int argc, char *argv[]){
  @autoreleasepool{
    return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));
  }
}
  • 那么 runLoop 是怎么實現(xiàn)的呢?
    下面用偽代碼來實現(xiàn)以下
int main(int argc, char *argc[]){
  int retVal = 0;
  do{
    // 睡眠中等待消息
    int message = sleep_and_wait();
    // 處理消息,處理完消息返回 0,繼續(xù) do...while 循環(huán)
    retVal = process_message(message);
  } while(0 == retVal);
  return 0;
}

RunLoop 對象

  • iOS中有2套API來訪問和使用RunLoop
    1、Foundation:NSRunLoop
    2、Core Foundation:CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表著RunLoop對象
  • NSRunLoop是基于CFRunLoopRef的一層OC包裝
  • CFRunLoopRef是開源的
    https://opensource.apple.com/tarballs/CF/

RunLoop 與線程

  • 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
  • RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
  • 線程剛創(chuàng)建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創(chuàng)建
  • RunLoop會在線程結(jié)束時銷毀
    1、獲取 Runloop的方式:
    [NSRunLoop currentRunLoop];獲取當前線程的 RunLoop 對象
    [NSRunLoop mainRunLoop];獲取主線程的 RunLoop 對象
    或者
    CFRunLoopGetCurrent();獲得當前線程的 RunLoop 對象
    CFRunLoopGetMain()獲得主線程的 Runloop 對象
  • 下面我們通過源碼來驗證上面的結(jié)論:
    由于 CFRunLoop 是開源的我們可以在源碼中找到CFRunLoopGetCurrent函數(shù)
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

上面我們看到其返回的是一個_CFRunLoopGet0函數(shù)

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());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
// 這里我們可以看到 loop 對象是從CFDictionaryGetValue這個字典中取出來的,而且是以線程指針作為 key,runloop 對象作為 value 的,
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
// 這里如果從字典中去到的loop 對象是空,那么就會創(chuàng)建一個新的線程對象
    if (!loop) { 
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 這里將新的 newLoop 對象賦值給 loop,并保存在字典中
    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 相關(guān)的類

  • Core Foundation中關(guān)于RunLoop的5個類
CFRunLoopRef :
  • CFRunLoopRef其實是一個__CFRunLoopRef類型的結(jié)構(gòu)體,其有兩個比較中要的變量CFRunLoopModeRef _currentMode; CFMutableSetRef _modes;
    _currentMode存放的是CFRunLoopModeRef類型的當前mode
    _modes存放的是當前RunLoop對象中所有的CFRunLoopModeRef類型的mode集合
    _commonModes 中存放的是所有標記為 commonModel 的集合,例如kCFRunLoopDefaultModeUITrackingRunLoopMode
    _commonModeItems中存放的是所有添加到kCFRunLoopCommonModes的所有對象,
    源碼如下:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
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;
};

其結(jié)構(gòu)如下:


image.png
CFRunLoopModeRef
  • 它其實是一個__CFRunLoopMode類型的結(jié)構(gòu)體,其有四個比較重要的變量CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers;
    _sources0_sources1中存放的是CFRunLoopSourceRef類型的變量;
    _observers中存放的是CFRunLoopObserverRef類型的變量;
    _timers中存放的是CFRunLoopTimerRef類型的變量;
    源碼如下:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
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 */
};
  • CFRunLoopModeRef 代表RunLoop的運行模式,一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
    ,RunLoop啟動時只能選擇其中一個Mode,作為currentMode,如果需要切換Mode,只能退出當前Loop,再重新選擇一個Mode進入,不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響,如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出。
目前已知的Mode有5種
  • kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
  • UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
  • UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到.
  • kCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode.添加到該模式下的事件源可以在所有標記為 commonMode的mode(kCFRunLoopDefaultMode、UITrackingRunLoopMode) 下運行
CFRunLoopSourceRef
  • _modes中的_sources0和_sources1的類型;
  • _sources0主要處理,觸摸時間和performSelector:onThread:類型的事件
    下面我們用源碼驗證一下,首先驗證觸摸事件的
    我們新建一個viewController中添加一個touchesBegan:方法,并在NSLog處打上斷點,
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"touchesBegan");
}

點擊屏幕觸發(fā)斷點,然后我們在控制臺執(zhí)行bt,得到函數(shù)調(diào)用棧信息如下:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000105516efd RunLoop`-[ViewController touchesBegan:withEvent:](self=0x00007f8b9e705a60, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600001884d80) at ViewController.m:25:5
    frame #1: 0x00007fff48bf59c6 UIKitCore`forwardTouchMethod + 323
    frame #2: 0x00007fff48bf5872 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
    frame #3: 0x00007fff48c0478f UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
    frame #4: 0x00007fff48c067f5 UIKitCore`-[UIWindow sendEvent:] + 4501
    frame #5: 0x00007fff48be0c39 UIKitCore`-[UIApplication sendEvent:] + 356
    frame #6: 0x00007fff48c6b096 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7328
    frame #7: 0x00007fff48c6e262 UIKitCore`__handleEventQueueInternal + 6565
    frame #8: 0x00007fff48c64dcb UIKitCore`__handleHIDEventFetcherDrain + 88
    frame #9: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #10: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
    frame #11: 0x00007fff23d9d5b4 CoreFoundation`__CFRunLoopDoSources0 + 180
    frame #12: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
    frame #13: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
    frame #14: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
    frame #15: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
    frame #16: 0x00000001055171c2 RunLoop`main(argc=1, argv=0x00007ffeea6e7cb8) at main.m:18:12
    frame #17: 0x00007fff519521fd libdyld.dylib`start + 1
    frame #18: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb) 

在frame #9中我們看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 可以驗證該事件源來自source0。

下面我們驗證performSelector:onThread:類型的,在viewDidLoad方法中加入如下代碼,并在test方法內(nèi)打上斷點

    dispatch_async(dispatch_get_global_queue(0,0), ^{
         [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:true];
    });
    
-(void)test{
    NSLog(@"test");
}

觸發(fā)斷點,在控制臺執(zhí)行bt,得到如下函數(shù)堆棧信息:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000102a14e27 RunLoop`-[ViewController test](self=0x00007fcaab50b2e0, _cmd="test") at ViewController.m:28:5
    frame #1: 0x00007fff2593af42 Foundation`__NSThreadPerformPerform + 209
    frame #2: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #3: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
    frame #4: 0x00007fff23d9d60c CoreFoundation`__CFRunLoopDoSources0 + 268
    frame #5: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
    frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
    frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
    frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
    frame #9: 0x0000000102a15152 RunLoop`main(argc=1, argv=0x00007ffeed1e9cb8) at main.m:18:12
    frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
    frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb) 

從frame #2中可以看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__驗證是source0。

  • 那么source1是干嘛的呢?source1主要是用來處理基于Port的線程間通信 和 系統(tǒng)事件捕捉的

  • source0 和 source1 有什么區(qū)別呢?
    Source1 :基于mach_Port的,來自系統(tǒng)內(nèi)核或者其他進程或線程的事件,可以主動喚醒休眠中的RunLoop(iOS里進程間通信開發(fā)過程中我們一般不主動使用)。mach_port大家就理解成進程間相互發(fā)送消息的一種機制就好, 比如屏幕點擊, 網(wǎng)絡(luò)數(shù)據(jù)的傳輸都會觸發(fā)sourse1。Source0 :非基于Port的 處理事件,什么叫非基于Port的呢?就是說你這個消息不是其他進程或者內(nèi)核直接發(fā)送給你的。一般是APP內(nèi)部的事件, 比如hitTest:withEvent的處理, performSelectors的事件。簡單舉個例子:一個APP在前臺靜止著,此時,用戶用手指點擊了一下APP界面,那么過程就是下面這樣的:
    我們觸摸屏幕,先摸到硬件(屏幕),屏幕表面的事件會被IOKit先包裝成Event,通過mach_Port傳給正在活躍的APP , Event先告訴source1(mach_port),source1喚醒RunLoop, 然后將事件Event分發(fā)給source0,然后由source0來處理。
    如果沒有事件,也沒有timer,則runloop就會睡眠, 如果有,則runloop就會被喚醒,然后跑一圈。
    我們可以這樣理解source1是系統(tǒng)事件,source0是應(yīng)用層事件,source1接收到事件后將事件分發(fā)給source0來處理

CFRunLoopTimerRef
  • _modes中的_timers的類型,主要處理定時器事件
    我們在viewDidLoad中添加下面源碼,并在test方法內(nèi)打上斷點,
[NSTimer scheduledTimerWithTimeInterval:3 repeats:true block:^(NSTimer * _Nonnull timer) {
        [self test];
    }];

觸發(fā)斷點,在終端中執(zhí)行bt,得到如下信息:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000102b44e27 RunLoop`-[ViewController test](self=0x00007fcc0bc0a490, _cmd="test") at ViewController.m:32:5
    frame #1: 0x0000000102b44d7b RunLoop`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x00006000018c02d0, timer=0x00006000023a8900) at ViewController.m:25:9
    frame #2: 0x00007fff2594462e Foundation`__NSFireTimer + 72
    frame #3: 0x00007fff23d9e634 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #4: 0x00007fff23d9e2ce CoreFoundation`__CFRunLoopDoTimer + 1038
    frame #5: 0x00007fff23d9d92a CoreFoundation`__CFRunLoopDoTimers + 282
    frame #6: 0x00007fff23d9857e CoreFoundation`__CFRunLoopRun + 1950
    frame #7: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
    frame #8: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
    frame #9: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
    frame #10: 0x0000000102b45152 RunLoop`main(argc=1, argv=0x00007ffeed0b9cb8) at main.m:18:12
    frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
    frame #12: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb) 

在frame #3:行中我們看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__驗證是處理定時器事件的。

CFRunLoopObserverRef
  • 用來監(jiān)聽RunLoop的狀態(tài)
  • 對應(yīng)狀態(tài)如下:

RunLoop是怎樣的一個循環(huán)

  • 流程如下:
01、通知Observers:進入Loop
02、通知Observers:即將處理Timers
03、通知Observers:即將處理Sources
04、處理Blocks
05、處理Source0(可能會再次處理Blocks)
06、如果存在Source1,就跳轉(zhuǎn)到第8步
07、通知Observers:開始休眠(等待消息喚醒)
08、通知Observers:結(jié)束休眠(被某個消息喚醒)
01> 處理Timer
02> 處理GCD Async To Main Queue
03> 處理Source1
09、處理Blocks
10、根據(jù)前面的執(zhí)行結(jié)果,決定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop
  • 上面我們在執(zhí)行斷點時在終端使用bt查看函數(shù)調(diào)用棧的時候都會看到CoreFoundation __CFRunLoopRun這個函數(shù),其實這個就是Runloop啟動的入口。在源碼CFRunLoop.c中可以找到CFRunLoopRun函數(shù),源碼如下:
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

可以看到源碼中就調(diào)用了CFRunLoopRunSpecific這個函數(shù),源碼如下:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
// 通知Observers:進入runloop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 進行runloop 的核心邏輯
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出runloop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

通過源碼看到返回的result是通過__CFRunLoopRun函數(shù)獲取的,__CFRunLoopRun函數(shù)源碼比較多,下面是提取出來的核心代碼

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();
// 先判斷是否是stopped狀態(tài),如果是返回stopped的狀態(tài)
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }

    int32_t retVal = 0;
    do {
        // 通知Observers 即將處理Timers
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知Observers 即將處理Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 處理Blocks,是NSRunLoop 中的 performBlock:函數(shù)中的block
        __CFRunLoopDoBlocks(rl, rlm);

        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {、
            // 處理Blocks,是NSRunLoop 中的 performBlock:函數(shù)中的block
            __CFRunLoopDoBlocks(rl, rlm);
        }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        //如果有Sources1,就跳轉(zhuǎn)打handle_msg標記處
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }

        // 通知Observers即將休眠
        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);
        // 休眠后進去do...while(1)的死循環(huán),循環(huán)里等待喚醒的消息,如果被喚醒執(zhí)行break跳出死循環(huán)
        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 {
                break;
            }
        } while (1);
        // 醒來
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopSetIgnoreWakeUps(rl);
        __CFRunLoopUnsetSleeping(rl);
        // 通知Observers 已經(jīng)被喚醒
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

        if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被timer喚醒,處理Timer
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        else if (livePort == dispatchPort) { // 被GCD 喚醒,處理GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

        } else { // 被sources1喚醒,處理Sources1
            if (rls) {
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }
        // 執(zhí)行Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 根據(jù)之前的執(zhí)行結(jié)果,來決定怎么做,如果retVal被標記為非零,當前循環(huán)結(jié)束,返回retVal,CFRunLoopRunSpecific中通知observers退出runloop,如果是Stopped或Finished則外層CFRunLoopRun中的do...while循環(huán)結(jié)束,代碼執(zhí)行完畢,線程結(jié)束
        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);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

  • GCD 如果是異步回到主線程的話是由runloop處理的
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"111-----%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"222-----%@",[NSThread currentThread]);
        });
    });

在NSLog(@"222-----%@",[NSThread currentThread]);處斷點,出發(fā)斷點后,終端執(zhí)行bt獲得函數(shù)調(diào)用棧如下

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x000000010f21dd70 RunLoop`__29-[ViewController viewDidLoad]_block_invoke_2(.block_descriptor=0x000000010f2200a0) at ViewController.m:31:33
    frame #1: 0x000000010f487f11 libdispatch.dylib`_dispatch_call_block_and_release + 12
    frame #2: 0x000000010f488e8e libdispatch.dylib`_dispatch_client_callout + 8
    frame #3: 0x000000010f496d97 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1149
    frame #4: 0x00007fff23d9da89 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    frame #5: 0x00007fff23d985d9 CoreFoundation`__CFRunLoopRun + 2041
    frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
    frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
    frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
    frame #9: 0x000000010f21e102 RunLoop`main(argc=1, argv=0x00007ffee09e1cb8) at main.m:18:12
    frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
    frame #11: 0x00007fff519521fd libdyld.dylib`start + 1

通過函數(shù)調(diào)用??吹狡涫峭ㄟ^CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE函數(shù)來處理的

runloop示意圖.png
線程保活

線程一旦執(zhí)行完線程內(nèi)的任務(wù)就會死亡,如果不想讓線程在執(zhí)行完任務(wù)之后死亡,就需要給線程的 runloop 的 mode 中添加source0、source1、timer、observer如果 runloop 中有四個中的任意一種就不會退出,runloop 不退出線程內(nèi)的任務(wù)就沒執(zhí)行完,就不會銷毀,我們可以給 runloop 添加一種事件源,讓其不退出

@interface ZWThread : NSThread

@end
@implementation ZWThread
- (void)dealloc {
    NSLog(@"%s", __func__);
}

@end
@interface ViewController ()
@property (strong, nonatomic) ZWThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self;
    self.stopped = NO;
    self.thread = [[ZWThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        // 往RunLoop里面添加Source\Timer\Observer, 這樣 runloop 不會立即退出
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.isStoped) { 
          // 這里運行 runloop,其實是"阻塞"線程,當 runloop 接收到事件時去處理事件,處理完了之后就休眠,當收到下次事件時重新判斷 while 的條件是否成立,如果成立,繼續(xù)執(zhí)行循環(huán)體,如果不成立跳出 while 循環(huán)
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        // 只有當 runloop stop 的時候才會走到這里打印,沒有 stop 的話,就一直在 while 循環(huán)體內(nèi)處理事件和休眠.
        NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    if (!self.thread) return;
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子線程需要執(zhí)行的任務(wù)
- (void)test{
    NSLog(@"after %s %@", __func__, [NSThread currentThread]);
}

- (IBAction)stop {
    if (!self.thread) return;
    // 在子線程調(diào)用stop(waitUntilDone設(shè)置為YES,代表子線程的代碼執(zhí)行完畢后,這個方法才會往下走)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

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

- (void)dealloc{
    NSLog(@"%s", __func__);
    
    [self stop];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一. RunLoop相關(guān) 什么是Runloop?顧名思義,Runloop就是運行循環(huán),就是在程序運行過程中循環(huán)做一...
    Imkata閱讀 558評論 0 2
  • iOS 中RunLoop 是一個事件循環(huán)對象 runloop跑一圈,只能執(zhí)行一個事件。 一般一個線程執(zhí)行任務(wù)完成后...
    小李不木閱讀 655評論 0 0
  • 前言 RunLoop是iOS和OSX開發(fā)中非?;A(chǔ)的一個概念,這篇文章將從CFRunLoop的源碼入手,介紹Run...
    暮年古稀ZC閱讀 2,404評論 1 19
  • RunLoop源碼剖析---圖解RunLoop 源碼面前,了無秘密 前言 我們在iOS APP中的main函數(shù)如下...
    祀夢_閱讀 1,006評論 0 13
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉(zhuǎn)變要...
    余生動聽閱讀 10,798評論 0 11

友情鏈接更多精彩內(nèi)容