iOS-OC底層-RunLoop

前言

RunLoop捶牢,又一個(gè)面試常常被問到的東西鸠珠, 它是什么?一個(gè)運(yùn)行循環(huán)秋麸,用來處理App中的各種任務(wù)渐排,當(dāng)有任務(wù)的時(shí)候,執(zhí)行任務(wù)灸蟆,沒任務(wù)的時(shí)候進(jìn)入休眠驯耻;每一個(gè)線程對(duì)應(yīng)一個(gè)Runloop,主線程的RunLoop默認(rèn)是開啟的,子線程的需要手動(dòng)開啟;每一個(gè)Runloop下對(duì)應(yīng)多個(gè)Modes炒考,每一個(gè)Mode下又有多個(gè)Items事務(wù).........這些在面試的時(shí)候基本上都能答得上來可缚,但我也相信大部分人都只是停留在這個(gè)概念上,并沒有深入的去探索過它斋枢。今天我就帶著大家來一步步探索一下帘靡。

Runloop初探

都說Runloop本質(zhì)上就是一個(gè)while循環(huán),那它跟普通的while循環(huán)有什么區(qū)別吶杏慰?下面是一個(gè)普通的while循環(huán):

int main(int argc, char * argv[]) {
    @autoreleasepool {
        while (1) {
            
        }
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));;
    }
}

運(yùn)行后觀察CPU的情況:

while循環(huán)的 CPU情況

再看下CFRunLoopRun的內(nèi)部實(shí)現(xiàn):

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

while內(nèi)部什么都沒有寫测柠,CPU就飆到97%,這實(shí)在可怕缘滥。而Runloop并沒有讓CPU飆升,說明它并沒有持續(xù)的占用CPU資源谒主,而是有事務(wù)的時(shí)候朝扼,執(zhí)行事務(wù),沒有事務(wù)的時(shí)候霎肯,進(jìn)入休眠狀態(tài)擎颖。
那Runloop都在處理哪些事務(wù)吶?
block應(yīng)用: __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
timer調(diào)用:__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__
接下來我們通過代碼直觀的感受一下观游,
1.首先來執(zhí)行一個(gè)NSTimer搂捧,通過bt查看堆棧信息:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self sourceDemo];
}
- (void)sourceDemo{
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"www.zezefamily.icu");
    }];
}
NSTimer事件
  1. Observer+touchesBegan:
- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(todoNotify:) name:@"heheh" object:nil];
}
#pragma mark - 觸摸事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
    NSLog(@"touchesBegan");
    [[NSNotificationCenter defaultCenter] postNotificationName:@"heheh" object:@"zezefamily"];
}
- (void)todoNotify:(NSNotification *)noti{
    // __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
    // NSLog(@"gotNotification = %@",noti);
}
source0+observer

這里先是處理了一個(gè)source0事件(touch),后處理observer(通知)。

  1. block
- (void)sourceDemo{
    void (^block)(void) = ^{
        NSLog(@"zezefamily");
    };
    block();
}
block事件
  1. queue
- (void)test{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"zezefamily");
    });
}
queue

官網(wǎng)文檔定義
RunLoop

Runloop與線程,Mode以及Item(事務(wù))之間的關(guān)系

這里我們使用CFRunLoop懂缕,而不是NSRunloop允跑,要知道的是NSRunloop本質(zhì)是CFRunLoop的對(duì)象化封裝,所以直接從CFRunLoop層來探索底層搪柑。這里通過CFRunLoop來獲取下當(dāng)前的運(yùn)行循環(huán)和主運(yùn)行循環(huán):

- (void)viewDidLoad {
    [super viewDidLoad];
    // 主運(yùn)行循環(huán)
    CFRunLoopRef mainRunloop = CFRunLoopGetMain();
    //當(dāng)前運(yùn)行循環(huán)
    CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
}

通過獲取返回的是一個(gè)CFRunLoopRef的類型聋丝,看一下它的內(nèi)部結(jié)構(gòu):

typedef struct __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;         // 當(dāng)前runloop 所在的線程
    uint32_t _winthread;         
    CFMutableSetRef _commonModes;         // 所有的_commonModes
    CFMutableSetRef _commonModeItems;    // 所有的事務(wù)
    CFRunLoopModeRef _currentMode;        // 當(dāng)前Mode,[CFRunLoopModeRef類型]
    CFMutableSetRef _modes;                     // 所有的modes
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

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 */
};

CFRunLoopRef 本質(zhì)是一個(gè)指向__CFRunLoop類型的結(jié)構(gòu)體指針。其中包含一個(gè)pthread_t _pthread; 的線程成員變量工碾,同時(shí)還包含了_commonModes弱睦,_commonModeItems, _currentMode, _modes
再看一下CFRunLoopGetMain()CFRunLoopGetCurrent()

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

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

都執(zhí)行了一個(gè)同樣的函數(shù)_CFRunLoopGet0(),這里就是獲取RunLoop對(duì)象的函數(shù)了:

static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    
    if (pthread_equal(t, kNilPthreadT)) { // 如果t== nil, t = main_queue()
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {   //字典不存在
        __CFUnlock(&loopsLock);
        //創(chuàng)建字典
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //創(chuàng)建runloop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // 進(jìn)行綁定 dict[@"pthread_main_thread_np"] = mainLoop   線程 = runLoop
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    //字典存在
    //根據(jù)線程在字典中取出對(duì)應(yīng)的Runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) { // 如果loop不存在
        //創(chuàng)建Runloop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        //判斷是否已經(jīng)保存在字典中
        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())) {   //如果線程t == 當(dāng)前線程 (這里也是一層緩存處理)
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    if (NULL == loop) {
    return NULL;
    }
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

_CFRunLoopGet0(pthread_t t)大致邏輯也比較簡(jiǎn)單渊额,首先會(huì)判斷__CFRunLoops(理解為全局的一個(gè)字典)是否存在况木,當(dāng)字典不存在垒拢,先創(chuàng)建字典,并通過__CFRunLoopCreate()函數(shù)創(chuàng)建一個(gè)mainLoop主運(yùn)行循環(huán)火惊,并通過key value 的方式保存入字典求类,key為線程,valuerunloop;
當(dāng)字典存在矗晃,首先根據(jù)pthread_t t這個(gè)key仑嗅,從字典中查詢對(duì)應(yīng)的loop對(duì)象,如果loop = nil(不存在)张症,通過__CFRunLoopCreate(t)創(chuàng)建loop對(duì)象仓技,并保存在全局的字典中;最后返回loop對(duì)象俗他。
這里再解讀一下__CFRunLoopCreate(pthread_t t)函數(shù):
主要是通過_CFRuntimeCreateInstance()創(chuàng)建一個(gè)CFRunLoopRef類型的對(duì)象loop脖捻,并進(jìn)行一系列的賦值操作,這里可以看到loop->_pthread = t;兆衅。
以上過程地沮,充分說明:RunLoop與線程是一一對(duì)應(yīng)的關(guān)系
通過LLDB看下RunLoop的輸出:

(lldb) po CFRunLoopCopyAllModes(mainRunloop)
<__NSArrayM 0x600002984ff0>(
UITrackingRunLoopMode,
GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode
)

(lldb) po CFRunLoopCopyCurrentMode(mainRunloop)
kCFRunLoopDefaultMode

通過這里的輸出羡亩,并結(jié)合struct __CFRunLoop摩疑,可以看到,一個(gè)RunLoop可包含多個(gè)Mode, 即RunLoop與Mode 是一對(duì)多的關(guān)系畏铆。
通過LLDB執(zhí)行po mainRunloop 來查看一下RunLoop的輸出雷袋,這里主要看下modes,以下是我整理的modes:

modes = <CFBasicHash 0x600003729da0 [0x7fff8062ce40]>{
    type = mutable set, count = 3,
    entries =>
        0 : <CFRunLoopMode 0x6000002700d0 [0x7fff8062ce40]>{name = UITrackingRunLoopMode, port set = 0x4e03, queue = 0x600001770900, source = 0x600001770a00 (not fired), timer port = 0x4c03, 
            sources0 = <CFBasicHash 0x600003747390 [0x7fff8062ce40]>{type = mutable set, count = 4,
                entries =>
                    0 : <CFRunLoopSource 0x600000c7c0c0 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff38b30a0e)}}
                    1 : <CFRunLoopSource 0x600000c78480 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000027c340, callout = __handleEventQueue (0x7fff48c64d04)}}
                    2 : <CFRunLoopSource 0x600000c78540 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003729aa0, callout = __handleHIDEventFetcherDrain (0x7fff48c64d73)}}
                    3 : <CFRunLoopSource 0x600000c7c540 [0x7fff8062ce40]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001d7f780, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff36c80bd5)}}
            },
            sources1 = <CFBasicHash 0x600003747450 [0x7fff8062ce40]>{type = mutable set, count = 1,
                entries =>
                2 : <CFRunLoopSource 0x600000c703c0 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3203, callout = PurpleEventCallback (0x7fff38b30a1a)}}
            },
            observers = (
                "<CFRunLoopObserver 0x60000087c0a0 [0x7fff8062ce40]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48bc10bc), context = <CFArray 0x60000372a520 [0x7fff8062ce40]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9fcf00c048>\n)}}",
                "<CFRunLoopObserver 0x600000874000 [0x7fff8062ce40]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff4874f06a), context = <CFRunLoopObserver context 0x600001274700>}",
                "<CFRunLoopObserver 0x60000087c1e0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff48bf1fb7), context = <CFRunLoopObserver context 0x7f9fce605120>}",
                "<CFRunLoopObserver 0x6000008743c0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b43cfa2), context = <CFRunLoopObserver context 0x0>}",
                "<CFRunLoopObserver 0x60000087c140 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff48bf2020), context = <CFRunLoopObserver context 0x7f9fce605120>}",
                "<CFRunLoopObserver 0x60000087c000 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48bc10bc), context = <CFArray 0x60000372a520 [0x7fff8062ce40]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9fcf00c048>\n)}}"
            ),
            timers = (null),
            currently 628410160 (3209085620118608) / soft deadline in: 1.8443535e+10 sec (@ -1) / hard deadline in: 1.8443535e+10 sec (@ -1)
        },

        1 : <CFRunLoopMode 0x6000002701a0 [0x7fff8062ce40]>{name = GSEventReceiveRunLoopMode, port set = 0x4b03, queue = 0x600001770a80, source = 0x600001770b80 (not fired), timer port = 0x4a03, 
            sources0 = <CFBasicHash 0x600003747510 [0x7fff8062ce40]>{
                type = mutable set, count = 1,
                entries =>
                    0 : <CFRunLoopSource 0x600000c7c0c0 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff38b30a0e)}}
            },
            sources1 = <CFBasicHash 0x6000037475d0 [0x7fff8062ce40]>{
                type = mutable set, count = 1,
                entries =>
                    2 : <CFRunLoopSource 0x600000c70480 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3203, callout = PurpleEventCallback (0x7fff38b30a1a)}}
            },
            observers = (null),
            timers = (null),
            currently 628410160 (3209085622357658) / soft deadline in: 1.8443535e+10 sec (@ -1) / hard deadline in: 1.8443535e+10 sec (@ -1)
        },

        2 : <CFRunLoopMode 0x60000027c000 [0x7fff8062ce40]>{name = kCFRunLoopDefaultMode, port set = 0x1f03, queue = 0x60000177d780, source = 0x60000177d880 (not fired), timer port = 0x2203, 
            sources0 = <CFBasicHash 0x600003747480 [0x7fff8062ce40]>{
                type = mutable set, count = 4,
                entries =>
                0 : <CFRunLoopSource 0x600000c7c0c0 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff38b30a0e)}}
                1 : <CFRunLoopSource 0x600000c78480 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000027c340, callout = __handleEventQueue (0x7fff48c64d04)}}
                2 : <CFRunLoopSource 0x600000c78540 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003729aa0, callout = __handleHIDEventFetcherDrain (0x7fff48c64d73)}}
                3 : <CFRunLoopSource 0x600000c7c540 [0x7fff8062ce40]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001d7f780, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff36c80bd5)}}
            },
            sources1 = <CFBasicHash 0x600003747540 [0x7fff8062ce40]>{
                type = mutable set, count = 1,
                entries =>
                    2 : <CFRunLoopSource 0x600000c703c0 [0x7fff8062ce40]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3203, callout = PurpleEventCallback (0x7fff38b30a1a)}}
                },
                observers = (
                "<CFRunLoopObserver 0x60000087c0a0 [0x7fff8062ce40]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48bc10bc), context = <CFArray 0x60000372a520 [0x7fff8062ce40]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9fcf00c048>\n)}}",
                "<CFRunLoopObserver 0x600000874000 [0x7fff8062ce40]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff4874f06a), context = <CFRunLoopObserver context 0x600001274700>}",
                "<CFRunLoopObserver 0x60000087c1e0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff48bf1fb7), context = <CFRunLoopObserver context 0x7f9fce605120>}",
                "<CFRunLoopObserver 0x6000008743c0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b43cfa2), context = <CFRunLoopObserver context 0x0>}",
                "<CFRunLoopObserver 0x60000087c140 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff48bf2020), context = <CFRunLoopObserver context 0x7f9fce605120>}",
                "<CFRunLoopObserver 0x60000087c000 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48bc10bc), context = <CFArray 0x60000372a520 [0x7fff8062ce40]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f9fcf00c048>\n)}}"
            ),
            timers = <CFArray 0x600001d7b0c0 [0x7fff8062ce40]>{type = mutable-small, count = 1, values = (
                    0 : <CFRunLoopTimer 0x600000c783c0 [0x7fff8062ce40]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 628409978 (-182.466853 @ 3208903165363796), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x7fff25925cc0 / 0x7fff480e99fb) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore), context = <CFRunLoopTimer context 0x600002c3ee00>}
                )},
            currently 628410160 (3209085622413965) / soft deadline in: 1.84467439e+10 sec (@ 3208903165363796) / hard deadline in: 1.84467439e+10 sec (@ 3208903165363796)
        },

}

這里可以看到,每一個(gè)Mode內(nèi)部又對(duì)應(yīng)著sources0,sources1,observers,timers等多個(gè)事務(wù)(items)辞居,最終就形成了如下的關(guān)系圖表:

Runloop,mode items關(guān)系圖

RunLoop處理事務(wù)的具體流程
- (viod)viewDidLoad
{
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("test_queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"zezefamily");
        }];
//        [[NSRunLoop currentRunLoop] run];
    });
}

上面代碼楷怒,創(chuàng)建了一個(gè)并發(fā)隊(duì)列,并添加了一個(gè)異步任務(wù)瓦灶,內(nèi)部添加了一個(gè)NSTimer定時(shí)器鸠删,這里很清楚的是,當(dāng)代碼執(zhí)行后贼陶,NSTimer并不會(huì)執(zhí)行刃泡,原因是NSTimer的回調(diào)依賴于RunLoop,而子線程的RunLoop默認(rèn)是沒有開啟的每界。所以當(dāng)執(zhí)行了[[NSRunLoop currentRunLoop] run];之后捅僵,NSTimer就開始正常執(zhí)行了。下一步就看一下眨层,RunLooprun是如何去觸發(fā)NSTimer的回調(diào)的庙楚。
[[NSRunLoop currentRunLoop] run];的本質(zhì)就是CFRunLoopRun(),看一下它的源碼:

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

在文章開頭趴樱,其實(shí)已經(jīng)看到過這段代碼馒闷,上面只是簡(jiǎn)單的認(rèn)識(shí)了下內(nèi)部是個(gè)do while循環(huán)酪捡,現(xiàn)在看下循環(huán)的內(nèi)部:核心是一個(gè)CFRunLoopRunSpecific()函數(shù):

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    CHECK_FOR_FORK();
    //判斷runloop狀態(tài)是否為IsDeallocating 如果是直接kCFRunLoopRunFinished
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //根據(jù)modeName 獲取 currentMode
    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);
    //將rl->_currentMode切換到currentMode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
    //判斷當(dāng)前活動(dòng)狀態(tài)是否=kCFRunLoopEntry
    if (currentMode->_observerMask & kCFRunLoopEntry ){
        //do Oserver
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        //__CFRunLoopRun
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    }
    //判斷當(dāng)前活動(dòng)狀態(tài)是否=kCFRunLoopExit
    if (currentMode->_observerMask & kCFRunLoopExit ){
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    }
    //將rl->_currentMode切換到previousMode(之前的模式)
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    
    return result;
}

上面的代碼,現(xiàn)實(shí)進(jìn)行了一些容錯(cuò)處理纳账;之后執(zhí)行mode切換逛薇,判斷RunLoop活動(dòng)狀態(tài)是否為kCFRunLoopEntry,如果是進(jìn)入result = __CFRunLoopRun();如果RunLoop活動(dòng)狀態(tài)為kCFRunLoopExit,則result = kCFRunLoopRunFinished疏虫。最后返回result永罚。
而正在處理事務(wù)(Items)邏輯的函數(shù)就是:
__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
這個(gè)函數(shù)的代碼又臭又長(zhǎng),非常不好閱讀卧秘,??

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun__CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
    rlm->_stopped = false;
    return kCFRunLoopRunStopped;
    }
    
    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
    
    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;
    
    
    
    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);

        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        __CFRunLoopDoBlocks(rl, rlm);

        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }

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

        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;

    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
        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);

        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

#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
        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
        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
        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
        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
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } 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
        
    __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;
    }
        
#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;
}

是不是一頭霧水呢袱,我把一些關(guān)鍵的代碼整理了一下,大致就是如下的樣子:

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);

    __CFRunLoopUnlock(rl);
    return result;
}

/// 核心函數(shù)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
   
    int32_t retVal = 0;
    do {  // itmes 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);
        }
        /// 判斷有無端口消息(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;
}

// main  dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

Run Loop Observer Activities

/* 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
};

最終就會(huì)形成這樣一張熟悉的流程圖:


Runloop事務(wù)處理流程圖
總結(jié)

摘出來的代碼有點(diǎn)多了遭顶,不過都是一些核心的代碼片段张峰。遺憾的是沒能斷點(diǎn)跟流程走一下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棒旗,一起剝皮案震驚了整個(gè)濱河市挟炬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗦哆,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婿滓,死亡現(xiàn)場(chǎng)離奇詭異老速,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凸主,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門橘券,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卿吐,你說我怎么就攤上這事旁舰。” “怎么了嗡官?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵箭窜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我衍腥,道長(zhǎng)磺樱,這世上最難降的妖魔是什么纳猫? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮竹捉,結(jié)果婚禮上芜辕,老公的妹妹穿的比我還像新娘。我一直安慰自己块差,他們只是感情好侵续,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著憨闰,像睡著了一般状蜗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上起趾,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天诗舰,我揣著相機(jī)與錄音,去河邊找鬼训裆。 笑死眶根,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的边琉。 我是一名探鬼主播属百,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼变姨!你這毒婦竟也來了族扰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤定欧,失蹤者是張志新(化名)和其女友劉穎渔呵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砍鸠,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扩氢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爷辱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片录豺。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饭弓,靈堂內(nèi)的尸體忽然破棺而出双饥,到底是詐尸還是另有隱情,我是刑警寧澤弟断,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布咏花,位于F島的核電站,受9級(jí)特大地震影響夫嗓,放射性物質(zhì)發(fā)生泄漏迟螺。R本人自食惡果不足惜冲秽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矩父。 院中可真熱鬧锉桑,春花似錦、人聲如沸窍株。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽球订。三九已至后裸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冒滩,已是汗流浹背微驶。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留开睡,地道東北人因苹。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像篇恒,于是被迫代替她去往敵國和親扶檐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • iOS 中RunLoop 是一個(gè)事件循環(huán)對(duì)象 runloop跑一圈胁艰,只能執(zhí)行一個(gè)事件款筑。 一般一個(gè)線程執(zhí)行任務(wù)完成后...
    小李不木閱讀 605評(píng)論 0 0
  • RunLoop 在 0202 年的今天其實(shí)已經(jīng)不是個(gè)新鮮的話題了,關(guān)于這方面的文章網(wǎng)上有很多大神總結(jié)得非常精辟腾么。 ...
    leejunhui閱讀 483評(píng)論 0 4
  • 手動(dòng)目錄RunLoop 6大響應(yīng)事件RunLoop 與線程的關(guān)系RunLoop狀態(tài)監(jiān)聽RunLoop 數(shù)據(jù)結(jié)構(gòu)Ru...
    Engandend閱讀 715評(píng)論 0 1
  • RunLoop基本作用: 1.保持程序持續(xù)運(yùn)行 2.處理App中的各種事件 3.節(jié)省CPU資源奈梳,提高程序性能 Ru...
    BabyNeedCare閱讀 294評(píng)論 0 0
  • 5月以來叫挟,哪怕對(duì)市場(chǎng)風(fēng)向再不敏感的人艰匙,也感覺到陣陣涼意。二級(jí)市場(chǎng)連續(xù)下挫抹恳,一級(jí)市場(chǎng)融資環(huán)境惡化员凝,不論企業(yè)融資數(shù)量還...
    錢皓頻道閱讀 6,055評(píng)論 1 6