RunLoop 源碼解析

蘋果對runloop的使用

蘋果在AutoreleasePool、手勢識別、事件響應、UI更新、定時器、NSObject延時調用方法(performSelecter:afterDelay: )等方面都有使用RunLoop。

runLoop 結構

  • 一個thread對應一個runloop
  • Cocoa層的NSRunLoop是對CF層的CFRunLoop的封裝
  • 一個runloop對應多個runLoopMode
  • 一個runloop一次只能執行一個runLoopMode,runloop在同一個時間只能且必須在一種特定mode下run。要想切換runLoopMode需要停止并退出當前RLM重新進入新的runLoopMode。
  • 一次執行一個mode的好處在于,底層設計相對簡單,避免不同的mode耦合在一起,代碼相互影響
  • 另一個好處是這樣可以在不同的mode下執行不同的代碼,避免上層業務代碼相互影響。
  • 多個mode以及mode的切換是iOS app滑動順暢的關鍵。
  • 主線程中不同的代碼指定在不同的mode下運行可以提高app的流暢度。
  • 每個runLoopMode包括若干個runLoopSource、若干個runLoopTimer、若干個runLoopObserver。

RunLoop結構體定義

// RunLoop的結構體定義
struct __CFRunLoop {
    pthread_mutex_t _lock;                      // 訪問mode集合時用到的鎖
    __CFPort _wakeUpPort;                           // 手動喚醒runloop的端口。初始化runloop時設置,僅用于CFRunLoopWakeUp,CFRunLoopWakeUp函數會向_wakeUpPort發送一條消息
    pthread_t _pthread;                             // 對應的線程
    CFMutableSetRef _commonModes;           // 集合,存儲的是字符串,記錄所有標記為common的modeName
    CFMutableSetRef _commonModeItems;   // 存儲所有commonMode的sources、timers、observers
    CFRunLoopModeRef _currentMode;      // 當前modeName
    CFMutableSetRef _modes;                     // 集合,存儲的是CFRunLoopModeRef
    struct _block_item *_blocks_head; // 鏈表頭指針,該鏈表保存了所有需要被runloop執行的block。外部通過調用CFRunLoopPerformBlock函數來向鏈表中添加一個block節點。runloop會在CFRunLoopDoBlock時遍歷該鏈表,逐一執行block
    struct _block_item *_blocks_tail; // 鏈表尾指針,之所以有尾指針,是為了降低增加block時的時間復雜度
};

RunLoop提供的主要API

以下API主要包括獲取runloop相關函數、runloop運行相關函數、操作source\timer\observer相關函數

// 獲取RunLoop
CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);

// 添加commonMode
CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode);

// runloop運行相關
CF_EXPORT void CFRunLoopRun(void);
CF_EXPORT SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
CF_EXPORT Boolean CFRunLoopIsWaiting(CFRunLoopRef rl);
CF_EXPORT void CFRunLoopWakeUp(CFRunLoopRef rl);
CF_EXPORT void CFRunLoopStop(CFRunLoopRef rl);

// source相關操作
CF_EXPORT Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
CF_EXPORT CFRunLoopSourceRef CFRunLoopSourceCreate(CFAllocatorRef allocator, CFIndex order, CFRunLoopSourceContext *context);
CF_EXPORT void CFRunLoopSourceSignal(CFRunLoopSourceRef source);

// observer相關操作
CF_EXPORT Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreateWithHandler(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, void (^block) (CFRunLoopObserverRef observer, CFRunLoopActivity activity)) CF_AVAILABLE(10_7, 5_0);

// timer相關操作
CF_EXPORT Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CF_EXPORT CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context);
CF_EXPORT CFRunLoopTimerRef CFRunLoopTimerCreateWithHandler(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, void (^block) (CFRunLoopTimerRef timer)) CF_AVAILABLE(10_7, 5_0);


/* 讓runloop執行某個block
 * 本質上是把block插入到一個由runloop維護的block對象組成的鏈表中,在runloop運行中取出鏈表里被指定在當前mode下運行的block,逐一執行。
 */
CF_EXPORT void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void)) CF_AVAILABLE(10_6, 4_0); 
RunLoop與線程關系

do-while 循環

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

結論

可以看到,runloop在run起來后其實是用一個do-while循環實現的,不同的是,runloop可以做到不需要處理事務的時候就sleep,需要的時候就work。其作用總結就是:
保持程序的持續運行
處理App中各種事件(觸摸、定時器、performSelector)
節省CPU資源、提供程序的性能:該做事做事,該休息休息

與線程的關系

##### mainRunloop
CFRunLoopRef mainRunloop = CFRunLoopGetMain();

##### CFRunLoopGetMain()函數內部實現
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;
}

##### _CFRunLoopGet0()函數內部實現
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        //建立全局的字典用來存儲 線程和runloop的關系
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //通過__CFRunLoopCreate()函數創建runloop實例對象
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //將mainLoop 和 mainThreadb存入字典
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }

    //主線程的loop在上面就已經創建了,所以如果獲取不到,那肯定是子線程的loop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    //沒有loop,那就創建子線程的loop對象
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //子線程和其runloop也會被存在這個全局的字典中
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&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);
        }
    }
    //返回runloop
    return loop;
}

結論

系統會創建一個全局的可變字典CFMutableDictionaryRef dict,儲存線程和runloop;
主線程的runloop是默認創建的
子線程的runloop默認不啟動,需要主動獲取

內部的mode-item關系

 __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;
    //有好多的Modes、Items
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    //但是只有一個currentMode
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    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];
    //有好多的事件源  _sources0、_sources1、_observers、_timers
    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對象內部包含多個modes
而每個mode中又包含了多個items
items就是事件源_sources0、_sources1、_observers、_timers...
但是__CFRunLoop對象同時只能持有一種mode:_currentMode

runloop有六大事務item,這些item都是由runloop調用的

GCD主隊列:
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
    _dispatch_main_queue_callback_4CF(msg);
    asm __volatile__(""); // thwart tail-call optimization
}

observer源:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (func) {
        func(observer, activity, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

timer:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
    if (func) {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

block: 
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
    if (block) {
        block();
    }
    asm __volatile__(""); // thwart tail-call optimization
}

響應source0
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) {
    if (perform) {
        perform(info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

響應source1
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
        mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
        void (*perform)(void *),
#endif
        void *info) {
    if (perform) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
        perform(info);
#endif
    }
    asm __volatile__(""); // thwart tail-call optimization
}

事務是如何被調用的

NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  • (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
    CFTypeRef item = (CFTypeRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
    CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
    CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
    CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}

CFRunLoopAddTimer()
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    //判斷當前loop的mode類型
    if (modeName == kCFRunLoopCommonModes) {
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //將timers添加到當前rl->_commonModeItems中
        CFSetAddValue(rl->_commonModeItems, rlt);
        //判斷set是否存在
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            //將這個timer 提交到當前loop的上下文中,方便后面使用去取
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {//其他類型也差不多,這里就不再細說
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
        }
        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
                rlt->_runLoop = rl;
            } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                return;
            }
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
}
 __CFRunLoopRun() 源碼太長,只展示部分
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;
    }
    ...
    //調用__CFRunLoopDoTimers()
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
      ...
}

  __CFRunLoopDoTimers()
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    //遍歷當前runloop中modes中的times加到CFMutableArrayRef timers對象中
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);

        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    //遍歷上面的CFMutableArrayRef timers,拿到具體每個timer
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        //調用timer
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

  __CFRunLoopDoTimer() 部分源碼
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;

    /* Fire a timer */
    CFRetain(rlt);
    __CFRunLoopTimerLock(rlt);

    //判斷是否還在超時時間內
    if (__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && !__CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {
        ...
        //最終調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__`
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
       ...
    }
    ...
}

RunLoop和Mode

mode作為runloop和source\timer\observer之間的橋梁。應用在啟動時main runloop會注冊5個mode。分別如下:

kCFRunLoopDefaultMode: App的默認 Mode,通常主線程是在這個 Mode 下運行的。
UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響。
UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用。
GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到。
kCFRunLoopCommonModes: 這是一個占位的 Mode,沒有實際作用。

個 RunLoop 包含若干個 Mode,每個 Mode 又可以包含若干個 Source/Timer/Observer。每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode就是runloop的 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。

image.png

mode中有一個特殊的mode叫做commonMode。commonMode并不是一個真正的mode,而是若干個被標記為commonMode的普通mode。所以commonMode本質上是一個集合,該集合存儲的是mode的名字,也就是字符串,記錄所有被標記為common的modeName。當我們向commonMode添加source\timer\observer時,本質上是遍歷這個集合中的所有的mode,把item依次添加到每個被標記為common的mode中(_commonModelItems)。每當RunLoop的內容發生變化時,RunLoop都會自動將——commonModeItems里的Source/Observer/Timer同步到具有“Common”標記的所有Mode的相對應的Source/Observer/Timer里。

在程序啟動時,主線程的runloop有兩個預置的mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。默認情況下是會處于defaultMode,滑動scrollView列表時runloop會退出defaultMode轉而進入trackingMode。所以,有時候我們加到defaultMode中的timer事件,在滑動列表時是不會執行的。不過,kCFRunLoopDefaultMode 和 UITrackingRunLoopMode這兩個 Mode 都已經被添加到runloop的commonMode集合中。也就是說,主線程的這兩個預置mode默認已經被標記為commonMode。想要我們的timer回調可以在滑動列表的時候依舊執行,只需要把timer這個item添加到commonMode。
另外可以通過調用CFRunLoopAddCommonMode 來添加一個模式到“common”模式集

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFStringRef _name;                          // mode名字
    Boolean _stopped;                           // mode的狀態,標識mode是否停止
    CFMutableSetRef _sources0;          // sources0事件集合
    CFMutableSetRef _sources1;          // sources1事件集合
    CFMutableArrayRef _observers;       // 觀察者數組
    CFMutableArrayRef _timers;          // 定時器數組
    CFMutableDictionaryRef _portToV1SourceMap;   //字典。key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;                       // 端口的集合。保存所有需要監聽的port,比如_wakeUpPort,_timerPort都保存在這個數組中
    CFIndex _observerMask;                  // 添加obsever時設置_observerMask為observer的_activities(CFRunLoopActivity狀態)
};

在Core Foundation中,針對Mode的操作,蘋果只開放了以下3個API(cocoa中也有功能一樣的函數,不再列出)

CF_EXPORT CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl);                    // 返回當前運行的mode的name
CF_EXPORT CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl);                            // 返回當前RunLoop的所有mode
CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode); // 向當前RunLoop的common modes中添加一個mode

我們沒有辦法直接創建一個CFRunLoopMode對象,但是我們可以調用CFRunLoopAddCommonMode傳入一個字符串向RunLoop中添加一個commonMode,傳入的字符串即為Mode的名字,Mode對象應該是此時在RunLoop內部創建的。

每次調用 RunLoop 的主函數時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入。

添加commonMode源碼

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    // 把modeName添加到RunLoop的_commonModes![CFRunLoopRun調用鏈.png](https://upload-images.jianshu.io/upload_images/1055199-a612b6b9ae4c2a9e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
中
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
        // 定義一個長度為2的數組context, 第一個元素是runloop,第二個元素是modeName
        CFTypeRef context[2] = {rl, modeName};
        // 把commonModeItems數組中的所有Source/Observer/Timer同步到新添加的mode(CFRunLoopModeRef實例)
        // 遍歷set集合中的每一個元素作為 __CFRunLoopAddItemsToCommonMode 的第一個參數,context 作為第二個參數,調用__CFRunLoopAddItemsToCommonMode
        CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        CFRelease(set);
    }
    } else {
    }
}

// 添加item到mode的item集合(數組)中
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
    CFTypeRef item = (CFTypeRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
      // item是source就添加到source"集合"中
    CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
      // item是observer就添加到observer"數組"中
    CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
      // item是timer就添加到timer"數組"中
    CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}

RunLoop和Source

__CFRunLoopSource也是一個結構體,其中有一個union屬性,它包含了version0和version1,也就是Source0(CFRunLoopSourceContext)和Source1(CFRunLoopSourceContext1)。

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;            /* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

一個source對應多個runloop。之所以使用CFMutableBagRef這種集合結構保存runloop而非array或set。主要原因是bag是無序的且允許重復(source對應的runloop是一個集合,說明source可以被添加到多個runloop中)(就像一個事件可以在不同的線程中被執行)

Source0分析

Source0是用來處理APP內部事件、APP自己負責管理,比如UIevent。
調用底層:因為source0只包含一個回調(函數指針)它并不能主動觸發事件;CFRunLoopSourceSignal(source)將這個事件標記為待處理;CFRunLoopWakeUp來喚醒runloop,讓他處理事件。首先創建一個Source0并添加到當前的runLoop中,執行信號,標記待處理CFRunLoopSourceSignal,再喚醒runloop去處理CFRunLoopWakeUp,通過CFRunLoopRemoveSource來取消移除源,CFRelease(rlp)。打印結果會顯示準備執行和取消了,終止了!!!,如果注釋掉CFRunLoopRemoveSource,則會打印準備執行和執行啦。

Source1分析

Source1被用于通過內核和其他線程相互發送消息。
__調用底層:__Source1包含一個 mach_port和一個回調(函數指針)
當NSPort對象接收到端口消息時,會調起handlePortMessage
端口接收到消息后會打印message內部屬性:localPort、components、remotePort等

- (void)setupPort{
    
    self.mainThreadPort = [NSPort port];
    self.mainThreadPort.delegate = self;
    // port - source1 -- runloop
    [[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];

    [self task];
}

- (void) task {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        self.subThreadPort = [NSPort port];
        self.subThreadPort.delegate = self;
        
        [[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }];
    
    [thread start];
}

- (void)funSendMessage{
    NSMutableArray* components = [NSMutableArray array];
    NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
    [components addObject:data];
    
    [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}

- (void)handlePortMessage:(id)message {
    NSLog(@"%@", [NSThread currentThread]); // 3 1

    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([message class], &count);
    for (int i = 0; i<count; i++) {
        
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
        NSLog(@"%@",name);
    }
    //睡眠一秒后,主線程再向子線程發送消息。
    sleep(1);
    if (![[NSThread currentThread] isMainThread]) {

        NSMutableArray* components = [NSMutableArray array];
        NSData* data = [@"woard" dataUsingEncoding:NSUTF8StringEncoding];
        [components addObject:data];

        [self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
    }
}

Source0和Source1區別

Source0:source0是App內部事件,由App自己管理的,像UIEvent、CFSocket都是source0。source0并不能主動觸發事件,當一個source0事件準備處理時,要先調用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理。然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。框架已經幫我們做好了這些調用,比如網絡請求的回調、滑動觸摸的回調,我們不需要自己處理。

Source1:由RunLoop和內核管理,Mach port驅動,如CFMachPort、CFMessagePort。source1包含了一個 mach_port 和一個回調(函數指針),被用于通過內核和其他線程相互發送消息。這種 Source 能主動喚醒 RunLoop 的線程。

添加source的源碼

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {
    // 導出runloop的commonMode(如果modeName是commonMode)
    if (modeName == kCFRunLoopCommonModes) {
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        // 初始化創建commonModeItems(如果_commonModeItems為空)
        if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
    // 把source添加到commonModeItems集合中
    CFSetAddValue(rl->_commonModeItems, rls);
    if (NULL != set) {
        // 創建一個長度為2的數組,分別存儲runloop和runloopSource
        CFTypeRef context[2] = {rl, rls};
        // 添加新的item也就是runloopSource到所有的commonMode中
        // set是commonMode集合,CFSetApplyFunction遍歷set,添加runloopSource到所有被標記為commonMode的mode->source0(或source1)中
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
    }
    } else {
    // 走到這里說明modeName不是commonMode
    // 根據modeName和runloop獲取runloop的mode
    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
    // 初始化創建runloopMode的source0 & source1這個集合(如果為空)
    if (NULL != rlm && NULL == rlm->_sources0) {
        rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
    }
    // 如果runloopMode的sources0集合和sources1都不包含將要添加的runloopSource則把runloopSource添加到對應的集合中
    if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
        if (0 == rls->_context.version0.version) {
            // rls是source0
            CFSetAddValue(rlm->_sources0, rls);
        } else if (1 == rls->_context.version0.version) {
            // rls是source1
            CFSetAddValue(rlm->_sources1, rls);
        __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
        if (CFPORT_NULL != src_port) {
            // key是src_port,value是rls,存儲到runloopMode的_portToV1SourceMap字典中
            CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
            __CFPortSetInsert(src_port, rlm->_portSet);
            }
        }
        __CFRunLoopSourceLock(rls);
        if (NULL == rls->_runLoops) {
            // source有一個集合成員變量runLoops。source每被添加進一個runloop,都會把runloop添加到他的這個集合中
            // 如官方注釋所言:sources retain run loops!(source會持有runloop!)
            rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
        }
        // 更新runloopSource的runLoops集合,將rl添加到rls->_runloops中
        CFBagAddValue(rls->_runLoops, rl);
        __CFRunLoopSourceUnlock(rls);
        // 如果rls是source0則doVer0Callout標記置為true,即需要向外調用回調
        if (0 == rls->_context.version0.version) {
            if (NULL != rls->_context.version0.schedule) {
                doVer0Callout = true;
            }
        }
    }
    }
    // 如果是source0,則向外層(上層)調用source0的schedule回調函數
    if (doVer0Callout) {
    rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
    }
}

把source添加到對應mode的source0或source1集合中。只是這里區分了下source被指定的mode是否為commonMode,如果source被指定的mode是commonMode,還需要把source添加到runloop的commonModeItems集合中

事件響應

image.png
1.硬件事件(觸摸/鎖屏/搖晃等)
2.IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收(SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event)
3.mach port轉發給APP進程
4.蘋果注冊的那個 Source1接收系統事件后在回調 __IOHIDEventSystemClientQueueCallback() 內觸發的 Source0
5.Source0 再觸發的 _UIApplicationHandleEventQueue()
6._UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理并包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow

RunLoop和timer

CFRunLoopTimer結構體

struct __CFRunLoopTimer {
    uint16_t _bits;                         // 標記fire狀態
    CFRunLoopRef _runLoop;          // timer所處的runloop
    CFMutableSetRef _rlModes;       // mode集合。存放所有包含該timer的mode的modeName,意味著一個timer可能會在多個mode中存在
    CFAbsoluteTime _nextFireDate;       // 下次觸發時間
    CFTimeInterval _interval;               // 理想時間間隔(不可變)
    CFTimeInterval _tolerance;      // 允許的誤差(可變)
    CFRunLoopTimerCallBack _callout;// timer回調
};

和source不同,timer對應的runloop是一個runloop指針,而非數組,所以此處說明一個timer只能添加到一個runloopMode下。

添加timer的源碼

作用:添加timer到rl->commonModeItems中,添加timer到runloopMode中,根據觸發時間調整timer在runloopMode->timers數組中的位置。
// 添加timer到runloopMode中,添加timer到rl->commonModeItems中

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    

    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    // 導出runloop的commonMode(如果modeName是commonMode)
    if (modeName == kCFRunLoopCommonModes) {
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        // 如果rl->_commonModeItems為空就初始化rl->commonModeItems
    if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
    CFSetAddValue(rl->_commonModeItems, rlt);
    if (NULL != set) {
        // 長度為2的數組,分別存放rl和rlt
        CFTypeRef context[2] = {rl, rlt};
        // 添加新的item也就是timer到所有的commonMode中
        // set是commonMode集合,CFSetApplyFunction遍歷set,添加context[1]存放的rlt添加到所有被標記為commonMode的mode中
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }
    } else {
      // 走到這里說明modeName不是commonMode
      // 根據runloop和modeName查找對用的mode
    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
    if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
    }
    if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
        rlt->_runLoop = rl;
        } else if (rl != rlt->_runLoop) {
        return;
        }
        // 更新rlt的rlModes集合。將rlm->name添加到name中
        CFSetAddValue(rlt->_rlModes, rlm->_name);
            // Reposition釋義復位。所以顧名思義該函數用于復位timer
            // 此處調用該函數本質上是按照timer下次觸發時間長短,計算timer需要插入到runloopMode->timers數組中的位置,然后把timer插入到runloopMode->timers數組中
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            // 為了向后兼容,如果系統版本低于CFSystemVersionLion且timer執行的rl不是當前runloop,則喚醒rl
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
    }
    }
}

設置timer下次觸發時間的源碼

void CFRunLoopTimerSetNextFireDate(CFRunLoopTimerRef rlt, CFAbsoluteTime fireDate) {
    // 觸發日期大于最大限制時間,則把觸發日期調整為最大觸發時間
    if (TIMER_DATE_LIMIT < fireDate) fireDate = TIMER_DATE_LIMIT;
    uint64_t nextFireTSR = 0ULL;
    uint64_t now2 = mach_absolute_time();
    CFAbsoluteTime now1 = CFAbsoluteTimeGetCurrent();
    // 下次觸發時間小于現在則立即觸發
    if (fireDate < now1) {
    nextFireTSR = now2;
    // 下次觸發時間間隔大于允許的最大間隔TIMER_INTERVAL_LIMIT,則將下次觸發時間調整為now + TIMER_INTERVAL_LIMIT
    } else if (TIMER_INTERVAL_LIMIT < fireDate - now1) {
    nextFireTSR = now2 + __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
    } else {
    nextFireTSR = now2 + __CFTimeIntervalToTSR(fireDate - now1);
    }
    __CFRunLoopTimerLock(rlt);
    if (NULL != rlt->_runLoop) {
        // 獲取runloopMode個數
        CFIndex cnt = CFSetGetCount(rlt->_rlModes);
        // 聲明名為modes的棧結構
        STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
        // rlt->rlModes賦值給modes棧結構
        CFSetGetValues(rlt->_rlModes, (const void **)modes);
        for (CFIndex idx = 0; idx < cnt; idx++) {
            // 先retain
            CFRetain(modes[idx]);
        }
        CFRunLoopRef rl = (CFRunLoopRef)CFRetain(rlt->_runLoop);
        // 把modes集合中存儲的modeName轉換為mode結構體實例,然后再存入modes集合
        for (CFIndex idx = 0; idx < cnt; idx++) {
        CFStringRef name = (CFStringRef)modes[idx];
            modes[idx] = __CFRunLoopFindMode(rl, name, false);
            // 后release
        CFRelease(name);
        }
        // 把上面計算好的下次觸發時間設置給rlt
    rlt->_fireTSR = nextFireTSR;
        rlt->_nextFireDate = fireDate;
        for (CFIndex idx = 0; idx < cnt; idx++) {
        CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
            if (rlm) {
                // Reposition釋義復位。所以顧名思義該函數用于復位timer,所謂復位,就是調整timer在runloopMode->timers數組中的位置
                // 此處調用該函數本質上是先移除timer,然后按照timer下次觸發時間長短計算timer需要插入到runloopMode->timers數組中的位置,最后把timer插入到runloopMode->timers數組中
                __CFRepositionTimerInMode(rlm, rlt, true);
            }
        }
        // 以上注釋的意思是:這行代碼的是為了給timer設置date,但不直接作用于runloop
        // 以防萬一,我們手動喚醒runloop,盡管有可能這個代價是高昂的
        // 另一方面,這么做的目的也是為了兼容timer的之前的實現方式
        // 如果timer執行的rl不是當前的runloop,則手動喚醒
        if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
     } else {
         // 走到這里說明timer的rl還是空,所以只是簡單的設置timer的下次觸發時間
    rlt->_fireTSR = nextFireTSR;
        rlt->_nextFireDate = fireDate;
     }
}
image.png

RunLoop的定時源,與Source1(Port)一樣,都屬于端口事件源,但不同的是,每一個Source1都有與之對應的端口,而一個RunLoopMode中的所有CFRunLoopTimer共用一個端口

解決定時器不準的問題

原因:NSTime和CADisplayLink底層都是基于RunLoop的CFRunLoopTimerRef的實現的,也就是說它們都依賴于RunLoop。如果RunLoop的任務過于繁重,會導致它們不準時。使用 GCD 的定時器。GCD 的定時器是直接跟系統內核掛鉤的,而且它不依賴于RunLoop,所以它非常的準時

RunLoop 和observer

Observer顧名思義,觀察者,和我們設計模式中的觀察者模式如出一轍。每個 Observer 都包含了一個回調(函數指針),observer主要觀察runloop的狀態變化,然后執行回調函數。runloop可觀察的狀態主要有6種狀態,如下:

// runloop的6種狀態,用于通知observer runloop的狀態變化
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),                   // 即將進入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),    // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2),   // 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),   // 即將進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),    // 剛從休眠中喚醒 但是還沒開始處理事件
    kCFRunLoopExit = (1UL << 7),                    // 即將退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
CFRunLoopObserver結構體定義
struct __CFRunLoopObserver {
    CFRunLoopRef _runLoop;                          // observer所觀察的runloop
    CFOptionFlags _activities;                  // CFOptionFlags是UInt類型的別名,_activities用來說明要觀察runloop的哪些狀態。一旦指定了就不可變。
    CFRunLoopObserverCallBack _callout; // 觀察到runloop狀態變化后的回調(不可變)
};
和source不同,observer對應的runloop是一個runloop指針,而非數組,此處說明一個observer只能觀察一個runloop,所以observer只能添加到一個runloop的一個或者多個mode中。

添加Observer源碼

void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
    if (modeName == kCFRunLoopCommonModes) {
        // 導出runloop的commonModes
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        // 初始化創建commonModeItems
    if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
        // 添加observer到commonModeItems
    CFSetAddValue(rl->_commonModeItems, rlo);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, rlo};
        // 添加observer到所有被標記為commonMode的mode中
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
    }
    } else {
    rlm = __CFRunLoopFindMode(rl, modeName, true);
    if (NULL != rlm && NULL == rlm->_observers) {
        rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
    }
    if (NULL != rlm && !CFArrayContainsValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo)) {
            Boolean inserted = false;
            for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
                CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
                if (obs->_order <= rlo->_order) {
                    CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
                    inserted = true;
                    break;
                }
            }
            if (!inserted) {
            CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo);
            }
        // 設置runloopMode的_observerMask為觀察者的_activities(CFRunLoopActivity狀態)
        rlm->_observerMask |= rlo->_activities;
        __CFRunLoopObserverSchedule(rlo, rl, rlm);
    }
        if (NULL != rlm) {
        __CFRunLoopModeUnlock(rlm);
    }
    }
    __CFRunLoopUnlock(rl);
}

自定義Observer來監聽runloop狀態變化

    /* 創建一個observer對象
     第一個參數: 告訴系統如何給Observer對象分配存儲空間
     第二個參數: 需要監聽的狀態類型
     第三個參數: 是否需要重復監聽
     第四個參數: 優先級
     第五個參數: 監聽到對應的狀態之后的回調
     */
    CFRunLoopObserverRef observer =  CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即將進入RunLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即將處理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即將處理source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即將進入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"從休眠中被喚醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即將退出RunLoop");
                break;
                
            default:
                break;
        }
    });
    

    /* 給主線程的RunLoop添加observer用于監聽runLoop狀態
     第一個參數:需要監聽的RunLoop對象
     第二個參數:給指定的RunLoop對象添加的監聽對象
     第三個參數:在那種模式下監聽
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

runloop 更新UI時機

image.png

runloop如何工作的

image.png

USE_DISPATCH_SOURCE_FOR_TIMERS 這個宏的值為 1,也就是說有使用 GCD 來實現 timer,當然 USE_MK_TIMER_TOO 這個宏的值也是 1,表示也使用了更底層的 timer。


image.png
// 一個do...while循環 如果不是stop或finish就不斷的循環 還可以重新啟動runloop
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
CFRunLoopRunInMode源碼
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
CFRunLoopRunSpecific源碼
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        // 如果runloop正在銷毀則直接返回finish
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    // 根據指定的modeName獲取指定的mode,也就是將要運行的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 出現以下情況就不會return finish:
    // 1>.將要運行的mode不為空
    // 以下這幾條是在__CFRunLoopModeIsEmpty函數中判斷的:
    // 2>.將要運行的currentMode是source0、source1、timer任一個不為空
    // 3>.待執行的block的mode和將要運行的mode相同
    // 4>.待執行的block的mode是commonMode且待運行的mode包含在commonMode中
    // 5>.待執行的block的mode包含待運行的mode
    // 6>.待執行的block的mode包含commonMode且待運行的mode包含在commonMode中
    // 所謂待執行的block是外部(開發者)通過調用CFRunLoopPerformBlock函數添加到runloop中的
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    return kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
    // 1.通知observer即將進入runloop
  // 這里使用currentMode->_observerMask 和 kCFRunLoopEntry按位與操作
  // 如果按位與的結果不是0則說明即將進入runloop
  // 而currentMode->_observerMask是個什么東西呢?
  // currentMode->_observerMask本質上是Int類型的變量,標識當前mode的CFRunLoopActivity狀態
  // 那么currentMode->_observerMask是在哪里賦值的呢?
  // 調用CFRunLoopAddObserver函數向runloop添加observer的時候會把observer的activities按位或賦值給mode->_observerMask
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
  // RunLoop的運行的最核心函數
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 10.通知observer即將退出runloop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    rl->_currentMode = previousMode;
    return result;
}
/**RunLoop的運行的最核心函數(進入和退出時runloop和runloopMode都會被加鎖)
 * rl: 運行的runloop
 * rlm: runloop Mode
 * seconds: runloop超時時間
 * stopAfterHandle: 處理完時間后runloop是否stop,默認為false
 * previousMode: runloop上次運行的mode
 */
static int32_t __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;
    }
    // 獲取主線程接收消息的port備用。如果runLoop是mainRunLoop且后續內核喚醒的port等于主線程接收消息的port,主線程就處理這個消息
    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
    // 初始化獲取timer的port(source1)
    // 如果這個port和mach_msg發消息的livePort相等則說明timer時間到了,處理timer
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    }
#endif
    // 使用GCD實現runloop超時功能
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    // seconds是設置的runloop超時時間,一般為1.0e10,11.574萬年,所以不會超時
    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;
    // returnValue 標識runloop狀態,如果returnValue不為0就不退出。
    // returnValue可能的值:
    // enum {
    //     kCFRunLoopRunFinished = 1,
    //     kCFRunLoopRunStopped = 2,
    //     kCFRunLoopRunTimedOut = 3,
    //     kCFRunLoopRunHandledSource = 4
    // };
    int32_t retVal = 0;
    do {
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
        // 消息緩沖區,用戶緩存內核發的消息
        uint8_t msg_buffer[3 * 1024];
        // 消息緩沖區指針,用于指向msg_buffer
        mach_msg_header_t *msg = NULL;
        // 用于保存被內核喚醒的端口(調用mach_msg函數時會把livePort地址傳進去供內核寫數據)
        mach_port_t livePort = MACH_PORT_NULL;
        __CFPortSet waitSet = rlm->_portSet;
        
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        // 2. 通知 Observers: RunLoop 即將觸發 Timer 回調。
        // __CFRunLoopDoObservers內部會調用__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__這個函數,這個函數的參數包括observer的回調函數、observer、runloop狀態
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 3. 通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調。
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //  執行被加入的block
        // 外部通過調用CFRunLoopPerformBlock函數向當前runloop增加block。新增加的block保存咋runloop.blocks_head鏈表里。
        // __CFRunLoopDoBlocks會遍歷鏈表取出每一個block,如果block被指定執行的mode和當前的mode一致,則調用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__執行之
        __CFRunLoopDoBlocks(rl, rlm);
        // 4. RunLoop 觸發 Source0 (非port) 回調
        // __CFRunLoopDoSources0函數內部會調用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函數
        // __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函數會調用source0的perform回調函數,即rls->context.version0.perform
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        // 如果rl處理了source0事件,那再處理source0之后的block
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        // 標記是否需要輪詢,如果處理了source0則輪詢,否則休眠
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            // 5. 如果有 Source1 (基于port的source) 處于 ready 狀態,直接處理這個 Source1 然后跳轉到第9步去處理消息。
            // __CFRunLoopServiceMachPort函數內部調用了mach_msg,mach_msg函數會監聽內核給端口發送的消息
            // 如果mach_msg監聽到消息就會執行goto跳轉去處理這個消息
            // 第五個參數為0代表不休眠立即返回
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }
        
        didDispatchPortLastTime = false;
        // 6. 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
        // 根據上面第4步是否處理過source0,來判斷如果也沒有source1消息的時候是否讓線程進入睡眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // runloop置為休眠狀態
        __CFRunLoopSetSleeping(rl);
        // 通知進入休眠狀態后,不要做任何用戶級回調
        __CFPortSetInsert(dispatchPort, waitSet);
        // 標記休眠開始時間
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        do {
            msg = (mach_msg_header_t *)msg_buffer;
            // 7. __CFRunLoopServiceMachPort內部調用mach_msg函數等待接受mach_port的消息。隨即線程將進入休眠,等待被喚醒。 以下事件會會喚醒runloop:
            // mach_msg接收到來自內核的消息。本質上是內核向我們的port發送了一條消息。即收到一個基于port的Source事件(source1)。
            // 一個timer的時間到了(處理timer)
            // RunLoop自身的超時時間到了(幾乎不可能)
            // 被其他調用者手動喚醒(source0)
            __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
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
        // 計算線程沉睡的時長
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        
        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);
        // runloop置為喚醒狀態
        __CFRunLoopUnsetSleeping(rl);
        // 8. 通知 Observers: RunLoop對應的線程剛被喚醒。
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        // 9. 收到&處理source1消息(第5步的goto會到達這里開始處理source1)
    handle_msg:;
        // 忽略端口喚醒runloop,避免在處理source1時通過其他線程或進程喚醒runloop(保證線程安全)
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            // livePort為null則什么也不做
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            // livePort為wakeUpPort則只需要簡單的喚醒runloop(rl->_wakeUpPort是專門用來喚醒runloop的)
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 9.1 如果一個 Timer 到時間了,觸發這個Timer的回調
            // __CFRunLoopDoTimers返回值代表是否處理了這個timer
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            /// 9.2 如果有dispatch到main_queue的block,執行block(也就是處理GCD通過port提交到主線程的事件)。
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            /// 9.3 如果一個 Source1 (基于port) 發出事件了,處理這個事件
            // 根據livePort獲取source(不需要name,從mode->_portToV1SourceMap字典中以port作為key即可取到source)
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                // 處理source1事件(觸發source1的回調)
                // runloop 觸發source1的回調,__CFRunLoopDoSource1內部會調用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                // 如果__CFRunLoopDoSource1響應的數據reply不為空則通過mach_msg 再send給內核
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
            }
        }
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        /// 執行加入到Loop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 進入loop時參數說處理完事件就返回。
            retVal = kCFRunLoopRunHandledSource; // 4
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// 超出傳入參數標記的超時時間了
            retVal = kCFRunLoopRunTimedOut; // 3
        } else if (__CFRunLoopIsStopped(rl)) {
            /// 被外部調用者強制停止了
            __CFRunLoopUnsetStopped(rl); // 2
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // 調用了_CFRunLoopStopMode將mode停止了
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped; // 2
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // source/timer/observer一個都沒有了
            retVal = kCFRunLoopRunFinished; // 1
        }
        // 如果retVal不是0,即未超時,mode不是空,loop也沒被停止,那繼續loop
    } while (0 == retVal);
    
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    
    return retVal;
}

__CFRunLoopRun部分源碼,do-while循環,先初始化一個存放內核消息的緩沖池,獲取所有需要監聽的port,設置RunLoop為可以被喚醒狀態,判斷是否有timer、source0、source1回調。如果有timer則通知 Observers: RunLoop 即將觸發 Timer 回調。如果有source0則通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調,執行被加入的block。RunLoop 觸發 Source0 (非port) 回調,再執行被加入的block。如果有 Source1 (基于port) 處于 ready 狀態,直接處理這個 Source1 然后跳轉去處理消息。例如一個Timer 到時間了,觸發這個Timer的回調。處理完后再次進入__CFArmNextTimerInMode查看是否有其他的timer。如果沒有事務需要處理則通知 Observers: RunLoop 的線程即將進入休眠(sleep),此時會進入一個內循環,線程進入休眠狀態mach_msg_trap(比如我們在斷點調試的時候),直到收到新消息才跳出該循環,繼續執行run loop。比如監聽到了事務基于 port 的Source 的事件、Timer 到時間了、RunLoop 自身的超時時間到了或者被其他什么調用者手動喚醒則喚醒。

image.png

CFRunLoopPerformBlock在上圖中作為喚醒機制有所體現,但事實上執行CFRunLoopPerformBlock只是入隊,下次RunLoop運行才會執行,而如果需要立即執行則必須調用CFRunLoopWakeUp。

以下是啟動 run loop 后比較關鍵的運行步驟:

  1. 通知 observers: kCFRunLoopEntry, 進入 run loop
  2. 通知 observers: kCFRunLoopBeforeTimers, 即將處理 timers
  3. 通知 observers: kCFRunLoopBeforeSources, 即將處理 sources
  4. 處理 blocks, 可以對 CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK 函數下斷點觀察到
  5. 處理 sources 0, 可以對 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION 函數下斷點觀察到
  6. 如果第 5 步實際處理了 sources 0,再一次處理 blocks
  7. 如果在主線程,檢查是否有 GCD 事件需要處理,有的話,跳轉到第 11 步
  8. 通知 observers: kCFRunLoopBeforeWaiting, 即將進入等待(睡眠)
  9. 等待被喚醒,可以被 sources 1、timers、CFRunLoopWakeUp 函數和 GCD 事件(如果在主線程)
  10. 通知 observers: kCFRunLoopAfterWaiting, 即停止等待(被喚醒)
  11. 被什么喚醒就處理什么:
    • 被 timers 喚醒,處理 timers,可以在 CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION 函數下斷點觀察到
    • 被 GCD 喚醒或者從第 7 步跳轉過來的話,處理 GCD,可以在 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE 函數下斷點觀察到
    • 被 sources 1 喚醒,處理 sources 1,可以在 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION 函數下斷點觀察到
  12. 再一次處理 blocks
  13. 判斷是否退出,不需要退出則跳轉回第 2 步
  14. 通知 observers: kCFRunLoopExit, 退出 run loop

GCD和RunLoop的關系

當調用 dispatch_async(dispatch_get_main_queue(), block) 時,libDispatch 會向主線程的RunLoop發送消息,RunLoop會被喚醒,并從消息中取得這個 block,并在回調 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里執行這個 block。但這個邏輯僅限于 dispatch 到主線程,dispatch 到其他線程仍然是由 libDispatch 處理的。那么你肯定會問:為什么子線程沒有這個和GCD交互的邏輯?原因有二:

主線程runloop是主線程的事件管理者。runloop負責何時讓runloop處理何種事件。所有分發個主線程的任務必須統一交給主線程runloop排隊處理。舉例:UI操作只能在主線程,不在主線程操作UI會帶來很多UI錯亂問題以及UI更新延遲問題。
子線程不接受GCD的交互。因為子線程不一定會有runloop。

AutoreleasePool和RunLoop的關系

App啟動后,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()。

第一個 Observer 監視的事件是 Entry(即將進入Loop),其回調內會調用 _objc_autoreleasePoolPush() 創建自動釋放池。其 order 是-2147483647,優先級最高,保證創建釋放池發生在其他所有回調之前。

第二個 Observer 監視了兩個事件: BeforeWaiting(準備進入休眠) 時調用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創建新池;Exit(即將退出Loop) 時調用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優先級最低,保證其釋放池子發生在其他所有回調之后。

在主線程執行的代碼,通常是寫在諸如事件回調、Timer回調內的。這些回調會被 RunLoop 創建好的 AutoreleasePool 環繞著,所以不會出現內存泄漏,開發者也不必顯示創建 Pool 了。

-

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容

  • 一直計劃著讀讀RunLoop的源碼,可是一直沒有行動,剛好這個周末沒啥事,就看看RunLoop的源碼吧。版本: C...
    Jack_deng閱讀 1,494評論 0 1
  • 什么是Runloop? Runloop不僅僅是一個運行循環(do-while循環),也是提供了一個入口函數的對象,...
    我叫Vincent閱讀 3,854評論 2 11
  • 寫在前面 由于文章比較長,簡書沒有目錄,讀起來不方便。建議看有目錄版RunLoop從源碼到應用全面解析——帶目錄版...
    紙簡書生閱讀 3,620評論 1 16
  • 1.主要的邏輯: while(傳過來的參數model參數 ){ while(model){ handleR...
    招牌豬閱讀 266評論 0 0
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經沒多少時間了。班主任說已經安排了三個家長分享經驗。 放學鈴聲...
    飄雪兒5閱讀 7,538評論 16 22