談談RunLoop底層

RunLoop是什么旧烧?

RunLoopiOS/Mac OS開發(fā)中比較重要的知識點,它貫穿程序運行的整個過程豫缨。它是線程基礎架構的一部分看靠,是一種保障線程循環(huán)處理事件而不會退出的機制。同時也負責管理線程需要處理的事件愕秫,讓線程有事兒時忙碌慨菱,沒事兒時休眠。

每個線程都有一個關聯(lián)的RunLoop對象戴甩,子線程的RunLoop是需要手動開啟的符喝,主線程的RunLoop作為應用啟動的一部分由系統(tǒng)自動開啟。

iOS/Mac OS提供了NSRunLoopCFRunLoopRef兩個對象甜孤,幫助我們配置和管理線程的RunLoop协饲。CFRunLoopRef提供純C實現(xiàn)并且線程安全的API;NSRunLoop是基于CFRunLoopRef封裝的面向對象的API缴川,這個API不是線程安全的茉稠。

RunLoop與線程的關系

蘋果是不建議我們自己創(chuàng)建RunLoop對象,但是我們可以通過下列方式獲取特定線程下的RunLoop對象:

[NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop];
//CoreFoundation
CFRunLoopGetMain();
CFRunLoopGetCurrent();

CoreFoundation是開源的(下載地址)把夸,我們可以查看CFRunLoopRef的關于CFRunLoopGetMainCFRunLoopGetCurrent的實現(xiàn):

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    // pthread_main_thread_np() 獲取主線程
    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;
    // pthread_self() 獲取當前線程
    return _CFRunLoopGet0(pthread_self());
}
///全局`Dictionary`
static CFMutableDictionaryRef __CFRunLoops = NULL;
///訪問`Dictionary`需要的鎖
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 = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    /// `Dictionary`不存在
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        /// 創(chuàng)建局部變量`Dictionary`
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        /// 【創(chuàng)建主線程的`RunLoop`對象】
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        /// 主線程對象的實際地址战惊,以該地址為`Key`,以`mainLoop`為`Value`存入局部變量`Dictionary`中
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        ///將局部變量`dict`的值 寫入全局字典`__CFRunLoops`對應的地址中
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    /// 從全局字典`__CFRunLoops`獲取線程對應的`RunLoop`
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    ///如果線程對應的`RunLoop`不存在
    if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        /// 創(chuàng)建`RunLoop`,取線程對象的實際地址扎即,
        /// 以該地址為`Key`吞获,以`newLoop`為`Value`存入全局變量`__CFRunLoops`中
        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())) {
        /// 將`RunLoop`對象以`__CFTSDKeyRunLoop`為`key`,儲存到線程的本地(私有)數據空間,
        ///析構函數為`NULL`
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        /// `CFInternal.h`定了枚舉`__CFTSDKeyRunLoop` = 10 與 `__CFTSDKeyRunLoopCntr` = 11 
        /// 如果線程TSD,枚舉`__CFTSDKeyRunLoopCntr` 對應`slot`未存值
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
        /// 為其存值`PTHREAD_DESTRUCTOR_ITERATIONS-1`,并設置析構函數`__CFFinalizeRunLoop`谚鄙,
        ///此舉目的是為了:當線程銷毀時各拷,實現(xiàn)對`RunLoop`的銷毀
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
       /// 如何實現(xiàn)的呢?請繼續(xù)往下看一探究竟闷营!
        }
    }
    return loop;
}

///上述代碼的`_CFGetTSD`與`_CFSetTSD`的實現(xiàn)如下:
///TSD: Thread Specific Data
typedef struct __CFTSDTable {
    uint32_t destructorCount;
    ///`uintptr_t` 存儲指針的烤黍,無符號整數類型,
    /// 數組個數為`CF_TSD_MAX_SLOTS`
    uintptr_t data[CF_TSD_MAX_SLOTS];
    tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;

// For the use of CF and Foundation only
CF_EXPORT void *_CFGetTSD(uint32_t slot) {
    // Get or initialize a thread local storage,It is created on demand
    __CFTSDTable *table = __CFTSDGetTable();
    //...
    uintptr_t *slots = (uintptr_t *)(table->data);
    return (void *)slots[slot];
}

// For the use of CF and Foundation only
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, tsdDestructor destructor) {
    /// Get or initialize a thread local storage,It is created on demand
    __CFTSDTable *table = __CFTSDGetTable();
    ///...
    void *oldVal = (void *)table->data[slot];
    ///...
    table->data[slot] = (uintptr_t)newVal;
    ///析構函數關聯(lián)
    table->destructors[slot] = destructor;
    return oldVal;
}
// Get or initialize a thread local storage. It is created on demand.
static __CFTSDTable *__CFTSDGetTable() {
   /// 通過`CF_TSD_KEY`獲取線程對應數據
    __CFTSDTable *table = (__CFTSDTable *)__CFTSDGetSpecific();
    // Make sure we're not setting data again after destruction.
    if (table == CF_TSD_BAD_PTR) {
        return NULL;
    }
    // Create table on demand
    if (!table) {
        // This memory is freed in the finalize function
        table = (__CFTSDTable *)calloc(1, sizeof(__CFTSDTable));
        // Windows and Linux have created the table already, we need to initialize it here for other platforms. On Windows, the cleanup function is called by DllMain when a thread exits. On Linux the destructor is set at init time.
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        ///初始化一個鍵`CF_TSD_KEY`傻盟,并關聯(lián)析構函數
        /// `CF_TSD_KEY` = 55 速蕊,在不同線程該`Key`值可以共享,但此`Key`對應的值卻是不同的。
        ///每個線程都會有自己的`tsd`娘赴,它們共用`CF_TSD_KEY`這個`key`
        /// 線程銷毀時蘋果系統(tǒng)會調用:
        /// `_pthread_exit` -> `_pthread_tsd_cleanup` -> 
        ///`_pthread_tsd_cleanup_new`->`_pthread_tsd_cleanup_key`
        ///當線程銷毀時规哲,會調用關聯(lián)的析構函數`__CFTSDFinalize`,保證線程對應的私有數據也能銷毀 
        ///具體可參照函數: _pthread_tsd_cleanup_key
        /// 函數實現(xiàn)[細節(jié)](https://github.com/apple/darwin-libpthread/blob/main/src/pthread_tsd.c)
        pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
#endif
        // 為`CF_TSD_KEY`指定需要存儲的數據
        __CFTSDSetSpecific(table);
    } 
    return table;
}

///銷毀線程對應的TSD
static void __CFTSDFinalize(void *arg) {
    ///...
    __CFTSDTable *table = (__CFTSDTable *)arg;
    ///遍歷所有插槽 比如存`RunLoop`的`__CFTSDKeyRunLoop`诽表,也有`__CFTSDKeyRunLoopCntr`的
    for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
        if (table->data[i] && table->destructors[i]) {
            uintptr_t old = table->data[I];
            table->data[i] = (uintptr_t)NULL;
           //遍歷到i= 11 =`__CFTSDKeyRunLoopCntr`時,調用`__CFFinalizeRunLoop`唉锌,釋放`RunLoop`
            table->destructors[i]((void *)(old));
        }
    }
    if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) {    // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
        free(table);
        ///...
        __CFTSDSetSpecific(CF_TSD_BAD_PTR);
        return;
    }
}

_CFGetTSD_CFSetTSD源碼查看可前往此處隅肥。

總結:

  1. RunLoop與線程之間是一一對應的
  2. 當線程需要獲取對應的RunLoop時,才會創(chuàng)建RunLoop對象
  3. 線程銷毀的時候會銷毀RunLoop對象

RunLoop的相關類

CoreFoundation中與RunLoop有關的5個結構體:

CFRunLoopRef //runLoop對象
CFRunLoopModeRef //runLoop運行的模式
CFRunLoopTimerRef// 基于時間的觸發(fā)器
CFRunLoopSourceRef//事件源袄简,source0:自定義事件輸入源 和 source1 :基于mach內核端口的事件源
CFRunLoopObserverRef //用于監(jiān)聽runLoop運行狀態(tài)的觀察者

它們之間的關系如下:

image.png

具體可通過打印[NSRunLoop currentRunLoop]查看腥放,也可通過查看CFRunLoopRefCFRunLoopModeRef的結構定義。兩者的結構定義如下:

///CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    //...
    CFStringRef _name;
    //...
    CFMutableSetRef _sources0; // <Set>
    CFMutableSetRef _sources1;// <Set>
    CFMutableArrayRef _observers; // <Array>
    CFMutableArrayRef _timers; // <Array>
    //...
};
///CFRunLoop.h 類型重命名
typedef struct __CFRunLoop * CFRunLoopRef;
///CFRunLoop.c 結構體
struct __CFRunLoop {
    //..
    CFMutableSetRef _commonModes; // <Set> String UITrackingRunLoopMode/kCFRunLoopDefaultMode
    CFMutableSetRef _commonModeItems;// <Set> observer/source/timer
    CFRunLoopModeRef _currentMode; //當前運行的mode
    CFMutableSetRef _modes; //內置的modes;
    //...
};

RunLoop的模式

每次運行RunLoop都需要指定一個Mode绿语,該Mode會被設置為_currentMode秃症,只有與該Mode關聯(lián)的輸入源source0source1才能被處理吕粹,同樣的伍纫,監(jiān)聽RunLoop的運行,只有與該Mode關聯(lián)的observers才能收到通知昂芜。

///`RunLoop`指定`Mode`運行
CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

程序在運行的過程中,會處理基于時間的赔蒲、系統(tǒng)的泌神、用戶的事件,這些事件在程序運行期間有著不同的優(yōu)先級舞虱,為了滿足應用層依據優(yōu)先級對這些事件的管理欢际,系統(tǒng)采用RunLoopMode對這些事件分組,然后交由RunLoop去管理矾兜。除了系統(tǒng)定義的默認模式和常用模式损趋,我們也可以自定義模式,但是自定義的模式中必須有關聯(lián)的事件椅寺,否則自定義模式沒有任何意義浑槽。

_commonModeItems_commonModeskCFRunLoopCommonModes (NSRunLoopCommonModes)背后的實現(xiàn)邏輯返帕,可以理解為采用RunLoopMode對事件進行分組后荆萤,我們又希望一些事件可以同時被多個Mode處理镊靴,于是我們將這些事件(sources/timers/observers)放入_commonModeItems链韭,將需要同時處理這些事件的多個Mode放入_commonModes集合進行標記;當事件指定kCFRunLoopCommonModes模式進行添加時敞峭,先會添加到_commonModeItems中踊谋,然后將_commonModeItems中的所有事件追加到_commonModes中已經標記的模式下褪子。

///添加一個`Mode`到`RunLoop`的`commonMode`集合中嫌褪,一旦添加無法移除笼痛。只加不減
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);

示例:主線程默認運行在kCFRunLoopDefaultMode下缨伊,當我們滑動ScrollView是會切換到UITrackingRunLoopMode刻坊,而主線程的RunLoop_commonModes默認包含這兩種模式谭胚;
開發(fā)中會遇到在主線程啟動一個定時器時灾而,會受視圖滑動的影響的問題旁趟,解決辦法:

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

將定時器添加到NSRunLoop對象的NSRunLoopCommonModes模式下锡搜,最終定時器會被添加到kCFRunLoopDefaultModeUITrackingRunLoopMode下耕餐;當然也可以自行添加到這兩個模式中蛾方。

查看CoreFoundationCFRunLoopAddTimer方法的實現(xiàn)桩砰,可以更深入的理解_commonModeItems_commonModes的工作原理:

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    ///...
    if (modeName == kCFRunLoopCommonModes) { ///是否是`kCFRunLoopCommonModes`
        ///取`Runloop`的`_commonModes`
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    if (NULL == rl->_commonModeItems) {
            ///創(chuàng)建`_commonModeItems`<Set>
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
        ///添加定時器到`_commonModeItems`
    CFSetAddValue(rl->_commonModeItems, rlt);
    if (NULL != set) { //`_commonModes`有值
        CFTypeRef context[2] = {rl, rlt};
        /* add new item to all common-modes */
            ///為Set集合中的每個元素都調用該方法
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }
    } else {
        ///非`kCFRunLoopCommonModes`,先找找`runloop`的modes是否有硼莽,找不到創(chuàng)建
    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
    if (NULL != rlm) {
            if (NULL == rlm->_timers) { ///創(chuàng)建存放`timer`的數組
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
    }
        /// mode有了偏螺,定時器對象的modes又沒有該mode
    if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            ///...
            if (NULL == rlt->_runLoop) {
        rlt->_runLoop = rl;
        } else if (rl != rlt->_runLoop) {
                 //...
                //定時器已關聯(lián)的runloop與當前runloop不一致套像,返回
        return;
        }
            ///定時器的modes 添加該mode的名稱
        CFSetAddValue(rlt->_rlModes, rlm->_name);
            //采用mktimer(mach kernel timer)通過machport 和 machmsg 觸發(fā)定時器事件
            __CFRepositionTimerInMode(rlm, rlt, false);
            ///...
    }
        ///...
    }
    ///..
}

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
    CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);//add source
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
    CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName); // add observer
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
    CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); // add timer
    }
}

CoreFoundation中向指定RunLoopMode中添加和移除事件的函數有:

//Source
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
//Timer
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
//Observer
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);

RunLoop的運行

當應用程序啟動的時候,主線程的RunLoop通過UIApplicationMain函數啟動柳譬。

image.png

通過程序啟動時,函數的調用棧制跟,發(fā)現(xiàn)調用了CFRunLoopRunSpecific
并且Mode之間的切換通過LLDB調試方式:b CFRunLoopRunSpecificb __CFRunLoopRun歼指,發(fā)現(xiàn)也會調用到該方法踩身,源碼分析如下:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    ///進程檢查
    CHECK_FOR_FORK();
    ///是否銷毀
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    ///互斥鎖
    __CFRunLoopLock(rl);
    //從`runloop`的`modes`找到`modeName`對應的`mode`琼娘,找不到也不創(chuàng)建
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    ///如果`currentMode`是空的,則返回`kCFRunLoopRunFinished`
    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`存儲當前rl的狀態(tài)
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
    ///`CurrentMode`的狀態(tài)為`kCFRunLoopEntry`時石景,
    ///通過`__CFRunLoopDoObservers`通知當前`Mode`對應的觀察者
    /// _observerMask 設置的是rlo需要監(jiān)聽的狀態(tài)
    ///1.runloop處于`kCFRunLoopEntry`潮孽,通知runloopmode->observers往史,runloop即將進入
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        ///`RunLoop`真正的運行邏輯
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
         ///`CurrentMode`的狀態(tài)為`kCFRunLoopExit`時挨决,
    ///通過`__CFRunLoopDoObservers`通知當前`Mode`對應的觀察者
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

///`RunLoop`運行的核心原理
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    /// TSR: time since repair
    uint64_t startTSR = mach_absolute_time();
    // 判斷`RunLoop`或`rlm->_stopped`是否已經停止脖祈,停止則執(zhí)行`return kCFRunLoopRunStopped`盖高。
    ///...
    ///聲明 mach_port,當(主線程的消息分發(fā)隊列是安全的&當前rl是主線程的rl&rlm->name in  rl-> commonModes)撞蚕,存放與主線程(主隊列)的runLoop關聯(lián)的`mach_port`甥厦,處理`runloop`內核事件
    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();
    ///  MacOS系統(tǒng)下矫渔,設置與Mode關聯(lián)的隊列對應的端口號(定時器隊列)
#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);
        ///...
    }
#endif
    ///  判斷參數`seconds`,決定`RunLoop`的運行時長油够,當(seconds>0&&seconds<=TIMER_INTERVAL_LIMIT),開啟GCD定時器石咬,其余情況 立即超時 和 超時不限
    dispatch_source_t timeout_timer = NULL;
    ///...
    timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
       ///...
    dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
       ///...
        dispatch_resume(timeout_timer);
        
     Boolean didDispatchPortLastTime = true;
    ///開啟do-While死循環(huán) 當retVal != 0 時 停止循環(huán)
    int32_t retVal = 0;
    do {
///...
        ////聲明msg_buffer數組
        uint8_t msg_buffer[3 * 1024];
///...
        ///rlm等待接收來自mach消息的mach_port集合
    __CFPortSet waitSet = rlm->_portSet;
        ///取消rl忽略喚醒的設置删性,使其能接收喚醒消息
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        ///2.runloop處于`kCFRunLoopBeforeTimers`蹬挺,通知runloopmode->observers巴帮,runloop即將觸發(fā)timer回調
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        ///3.runloop處于`kCFRunLoopBeforeSources`榕茧,通知runloopmode->observers,runloop即將觸發(fā)Source0(非mach_port)回調
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        ///執(zhí)行runloop通過`struct _block_item *_blocks_head蜻拨、_blocks_tail`加入runloop的block; 
        ///最終調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__`
    __CFRunLoopDoBlocks(rl, rlm);
        /// 4. 執(zhí)行自定義的`source0`事件官觅,最終調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__`
        // `stopAfterHandle`A flag indicating whether the run loop should exit after processing one source 
        /// 如果rl立即超時或者source0已經處理完畢(rl退出)
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {///source0處理完畢咱圆,再次執(zhí)行被加入的block
            __CFRunLoopDoBlocks(rl, rlm);
    }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        ///主線程`runloop`的mach_port有效且不是首次分配
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
        ///...
            msg = (mach_msg_header_t *)msg_buffer;
            ///5.`thread`開啟`for(;;)`循環(huán)序苏,等待围来,
            ///通過`mach_msg`等待從rl的`dispatchPort`獲取信息监透,如果成功獲取胀蛮,則跳轉處理source1粪狼。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                ///處理msg
                goto handle_msg;
            }
            ///....
        }
        didDispatchPortLastTime = false;
        ///如果rl沒有退出 && 處于`kCFRunLoopBeforeWaiting`狀態(tài)
        ///6.runloop處于`kCFRunLoopBeforeWaiting`狡刘,通知runloopmode->observers颓帝,runloop即將進入休眠(sleep)
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        ///設置runloop的狀態(tài)為sleep购城,此處設置1
        ///Bit 0 of the base reserved bits is used for stopped state
        ///Bit 1 of the base reserved bits is used for sleeping state
        ///Bit 2 of the base reserved bits is used for deallocating state
    __CFRunLoopSetSleeping(rl);
        ///加入rlm的waitset中
        __CFPortSetInsert(dispatchPort, waitSet);
        ///...
        ///設置rl休眠開始的時間,rl退出為0 否則為當前絕對時間
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        ///...
        ///7. 通過`__CFRunLoopServiceMachPort`執(zhí)行`if(TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } 
        ///讓線程進入休眠侮攀,等待被`mach_msg`函數喚醒
        msg = (mach_msg_header_t *)msg_buffer;
        ///參數超時時間為`TIMEOUT_INFINITY`觸發(fā)rl的sleep,(rlm的portSet) poll = false 標識rl 未停止,未超時 waitSet 還有定時器port
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        ///macOS會循環(huán)執(zhí)行rlm->queue中事件畦贸,直到all done
    ///...
        ///rl被喚醒薄坏,計算rl的休眠時間
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        ///從rlm的waitSet(portSet)中移除
        __CFPortSetRemove(dispatchPort, waitSet);
        ///rl已被喚醒,故設置rl忽略喚醒消息
        __CFRunLoopSetIgnoreWakeUps(rl);
        // user callouts now OK again
        ///取消rl的sleeping狀態(tài)
    __CFRunLoopUnsetSleeping(rl);
        ///如果rl沒有退出 && 處于`kCFRunLoopAfterWaiting`狀態(tài) 
        ///8.runloop處于`kCFRunLoopAfterWaiting`沈善,通知runloopmode->observers瞳脓,runloop即將被喚醒
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        ///收到來自mach_port的消息會跳轉此處執(zhí)行
        handle_msg:;
        ///rl已被喚醒劫侧,故設置rl忽略喚醒消息
        __CFRunLoopSetIgnoreWakeUps(rl);
        ///....
        ///9.被喚醒處理事件
        ///`__CFRunLoopServiceMachPort`調用`mach_msg`成功烧栋,會設置`livePort`的值為消息來源的端口
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();///// livePort為空,do nothing
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {/// 通過調用`CFRunLoopWakeUp`函數喚醒rl
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();//
            // do nothing on Mac OS
        }
        /// 9.1 被定時器喚醒魔吐,處理定時器事件
        ///被GCD Timer喚醒
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            ///如果未處理酬姆,重設下次觸發(fā)時間
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        ///被MK Timer喚醒
#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())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
       ///9.2 處理dispatch到mainQueue的block事件
        else if (livePort == dispatchPort) {
           /// DISPATCH 喚醒 runloop
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
           ///線程Data以`__CFTSDKeyIsInGCDMainQ`為key 存 6
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
            ///`_dispatch_main_queue_callback_4CF`,處理msg
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            ///線程Data以`__CFTSDKeyIsInGCDMainQ`為key 存 0,用來在函數開始時判斷`libdispatchQSafe`的值立美。
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            ///..
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            ///9.3 被基于mach_port的source1喚醒建蹄,處理此事件
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
             ///...
            /// 從rlm->_portToV1SourceMap的字典中针贬,取出Source1事件
            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;
                ///處理Source1桦他,調用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__`
        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        if (NULL != reply) {///處理完source1圆仔,如果需要回復消息坪郭,則執(zhí)行消息回復
            (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
            CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
        }
                ///...
#endif
        }
            ///...
        } 
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        ////執(zhí)行一下加入runloop的blocks
    __CFRunLoopDoBlocks(rl, rlm);
        
    if (sourceHandledThisLoop && stopAfterHandle) {///source處理完畢&處理完畢需要停止runloop
        retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {///已經超時
            retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {///通過`CFRunLoopStop`函數設置rl狀態(tài)為STOPPED
            __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {///runLoopMode已經停止
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { ///rkm為空嗦锐,
            ///沒有一個timer或source0或source1 或者rl沒有block需要執(zhí)行,并且不是主隊列
        retVal = kCFRunLoopRunFinished;
    }
        //...
    } while (0 == retVal);
     ///...
    return retVal;
}

RunLoop運行函數內部是一個do-while循環(huán)碳默,讓線程持續(xù)運行嘱根,接收事件,處理事件柔逼;RunLoop定義了一些狀態(tài)愉适,當它在特定RunLoopMode下運行時,可以向該Mode下注冊的觀察者發(fā)送消息癌蓖;

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),///進入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),///RunLoop即將觸發(fā)定時器事件
    kCFRunLoopBeforeSources = (1UL << 2),///RunLoop即將處理Source事件
    kCFRunLoopBeforeWaiting = (1UL << 5),///RunLoop即將進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),///RunLoop即將被喚醒,但尚未開始處理喚醒它的事件
    kCFRunLoopExit = (1UL << 7),///退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

蘋果文檔中總結了RunLoop運行時的事件的執(zhí)行序列用僧,大致如下:

  1. 通知觀察者糟港,RunLoop即將進入
  2. 通知觀察者秸抚,RunLoop即將觸發(fā)Timer
  3. 通知觀察者耸别,RunLoop即將處理Source0(非mach_port
  4. 觸發(fā)任何準備觸發(fā)的非基于端口的Source0輸入源
  5. 如果基于mach_port的輸入源Source1已經就緒等待觸發(fā),則跳轉第9步處理Source1.
  6. 通知觀察者省有,RunLoop即將進入休眠
  7. 讓線程進入休眠,直到發(fā)生一下事件之一:
    • 基于mach_port的輸入源Source1發(fā)生舷蟀;
    • 定時器觸發(fā);
    • RunLoop設置的timeout生效匈子,運行即將結束;
    • RunLoop被顯式喚醒其徙,調用CFRunLoopWakeUp;
  8. 通知觀察者唾那,RunLoop即將被喚醒
  9. 喚醒后,處理待處理的事件:
    • 用戶定義的定時器啟動昌罩,跳轉第2步,處理定時器事件轨功,重新開始循環(huán)(2 ~ 9
    • 處理基于端口的輸入源,傳遞收到的消息羡滑。
    • RunLoop被顯式喚醒但還沒超時柒昏,跳轉第2步,重新開始循環(huán)(2 ~ 9
  10. 通知觀察者有梆,RunLoop退出

RunLoop退出時,運行函數會返回下列枚舉值:

typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4 
};
  1. Source處理完畢并且需要立即停止runloop時,返回kCFRunLoopRunHandledSource陨囊,退出RunLoop;
  2. RunLoop設置的timeout生效夹攒,返回kCFRunLoopRunTimedOut蜘醋,退出`RunLoop;
  3. 顯式調用CFRunLoopStop函數,設置RunLoop狀態(tài)為STOPPED咏尝,返回kCFRunLoopRunStopped压语,退出RunLoop;
  4. RunLoop運行的Mode是停止狀態(tài)啸罢,返回kCFRunLoopRunStopped胎食,退出RunLoop;
  5. RunLoop運行的Mode為空扰才,沒有timersource0source1,或者runloop沒有需要執(zhí)行的block返回kCFRunLoopRunFinished厕怜,退出RunLoop;

最后再通過一張圖衩匣,總結下RunLoop內部運行邏輯,大致如下:

image.png

RunLoop的應用

Oberserver

CoreFoundationObserver的結構:

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount; ///被添加到Rl幾次
    CFOptionFlags _activities;//需要觀察RL哪些狀態(tài)
    CFIndex _order;///狀態(tài)事件觸發(fā)時粥航,依此通知的觀察者琅捏,值越小優(yōu)先級越高 
    CFRunLoopObserverCallBack _callout; //狀態(tài)事件觸發(fā)時的回調
    CFRunLoopObserverContext _context;// 上下文    
};
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

觀察者,可以被添加到RunLoop的多個Mode下递雀,同一個Mode下可以有多個Oberserver柄延,當事件觸發(fā)時,觀察者是依據_order值從小到大的順序進行事件回調的缀程。

示例: 創(chuàng)建滑動視圖并為主線程RunLoopkCFRunLoopCommonModes (NSRunLoopCommonModes)添加Observer拦焚,觀察RunLoop的切換。

///設置觀察者
- (void)addObserverForMainRunLoop {
    /*
     UITrackingRunLoopMode,GSEventReceiveRunLoopMode,
     kCFRunLoopDefaultMode,kCFRunLoopCommonModes
     */
    void *info = (__bridge_retained void *)self;
    CFRunLoopObserverContext context = {0,info,NULL,NULL,NULL};
   //一個優(yōu)先級索引杠输,指示處理運行循環(huán)觀察者的順序赎败。在給定的運行循環(huán)模式下,當多個運行循環(huán)觀察者被調度在同一活動階段時蠢甲,觀察者按此參數的遞增順序進行處理僵刮。傳遞 0,除非有理由不這樣
    CFRunLoopObserverRef changeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault ,
                                                                  kCFRunLoopAllActivities,
                                                                  YES,
                                                                  0,
                                                                  &_runLoopObserverCallBack,
                                                                  &context);
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), changeObserver, kCFRunLoopCommonModes);
    CFRelease(changeObserver);
}
///回調函數
void _runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    ///獲取mode名稱
    NSString* mode = (__bridge NSString*)CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
    if (info) {//橋接info為oc對象 
    }
    switch (activity) {
        //do somthing...
    }
}

Source0

CoreFoundationSource的結構:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order; ///同observer
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0; //source0
        CFRunLoopSourceContext1 version1; //source1
    } _context;
};
typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

typedef struct {
    ///...同`CFRunLoopSourceContext`前7個屬性
#if TARGET_OS_OSX || TARGET_OS_IPHONE
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    ///...
#endif
} CFRunLoopSourceContext1;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

蘋果系統(tǒng)定義了一些API,底層是基于Source0實現(xiàn)的:

///子線程->主線程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
///主線程->子線程鹦牛,    
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array 
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait 
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg 

不過需要注意當調用主線程->子線程序系列方法時搞糕,務必要保證子線程的RunLoop是開啟的,否則不會生效曼追。

示例: 我們基于Souce0簡單模仿下performSelector從主線程發(fā)送消息給子線程窍仰。

///1.開啟子線程
 _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程的方法中,添加一個`Souce0`事件礼殊,并開啟`RL`
- (void)subthreadOperation {
    ///3.保存子線程的runloop
    _subRunLoop = CFRunLoopGetCurrent();
    ///4.創(chuàng)建&添加source0
    void *info = (__bridge_retained void*)self;
    CFRunLoopSourceContext context = {0,info,NULL,NULL,NULL,NULL,NULL,&schedule,&cancel,&perform};
    _source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    CFRunLoopAddSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
    ///運行RL
    [[NSRunLoop currentRunLoop] run];
}
///與Source0相關的回調
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    ///source0已加入子線程的runloop中
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    ///source0已從子線程的runloop中移除
}
void perform(void *info) {
   ///橋接info,獲取來自主線程的消息
}
///5.主線程觸發(fā)Source0事件
- (void)buttonAction:(id)sender {
    //5.1 觸發(fā)source
    CFRunLoopSourceSignal(_source);
    ///5.2 喚醒runLoop
    CFRunLoopWakeUp(_subRunLoop);
}
///6.移除Source0,退出子線程RL
Boolean contain = CFRunLoopContainsSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
if (contain) {
    //6.1移除source
    CFRunLoopRemoveSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
    ///6.2停止runLoop
    CFRunLoopStop(_subRunLoop);
}

總結: 所謂Souce0只不過是被包裝的帶有上下文的函數驹吮,需要主動觸發(fā),這個函數才會被執(zhí)行晶伦。

Source1

Sorce1是基于mach_port的事件碟狞,它是內核事件,蘋果系統(tǒng)的內核是XNU混合內核婚陪,包括了Mach內核和BSD內核族沃,BSD主要提供在Mach之上標準化的APIMach才是核心,負責線程與進程管理脆淹、虛擬內存管理常空、進程通信與消息傳遞、任務調度等盖溺。

基于Source1的事件傳遞漓糙,主要依托于內核接口:

///System Trap / Function — Sends and receives a message using the same mes- sage buffer
mach_msg_return_t mach_msg(mach_msg_header_t *msg, mach_msg_option_t option, mach_msg_size_t send_size, mach_msg_size_t rcv_size, mach_port_name_t rcv_name, mach_msg_timeout_t timeout, mach_port_name_t notify)

這個方法底層會基于硬件異常:陷阱(trap)實現(xiàn)事件傳遞。陷阱最終的用途咐柜,是在用戶程序和內核之間提供一個像過程一樣的接口兼蜈,稱為系統(tǒng)調用

macOS系統(tǒng)中攘残,可以使用基于mach_portSource1實現(xiàn)進程通信拙友。

示例: 創(chuàng)建一個子線程,通過Source1建立主線程與子線程信道歼郭,實現(xiàn)雙向通信遗契。

///1.開啟子線程
 _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(launchThreadWithPort:) object:nil];
///2.子線程的方法中,添加一個`Souce1`事件病曾,并開啟`RL`
- (void)launchThreadWithPort:(NSPort*)port {
    @autoreleasepool {
        ///3.創(chuàng)建&添加Source1
        void *info = (__bridge_retained void*)self;
        CFMessagePortContext portcontext = {0,info,NULL,NULL,NULL};
        Boolean shouldFreeInfo;
        CFMessagePortRef mach_port = CFMessagePortCreateLocal(kCFAllocatorDefault,  CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("com.qishare.sub_mach_port")), &_messagePortCallBack, &portcontext, &shouldFreeInfo);
        ///保存端口牍蜂,建立雙向信道
        _subPort = mach_port;
        if (mach_port != NULL) {
            CFRunLoopSourceRef source1 = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mach_port, 0);
            if (source1 != NULL) {
                CFRunLoopAddSource(CFRunLoopGetCurrent(), source1, kCFRunLoopDefaultMode);
                CFRunLoopRun();
                CFRelease(source1);
                CFRelease(mach_port);
            }
        }
    }
}
///3.1`Source1`的回調函數
CFDataRef _messagePortCallBack(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
    ///橋接info獲取oc對象...
    const UInt8 *buffer = CFDataGetBytePtr(data);
    CFIndex index = CFDataGetLength(data);
    CFStringRef messageref = CFStringCreateWithBytes(kCFAllocatorDefault, buffer, index, kCFStringEncodingUTF8, false);
    NSString *message = (__bridge_transfer NSString*)messageref;
    NSString *tip =  msgid == 1002 ? @"主線程" : @"子線程";
    NSLog(@"%@:%@,收到數據:%@", tip,[NSThread currentThread],message);
    return NULL;
}
///5.消息發(fā)送:子線程->主線程
[self performSelector:@selector(sendMsgToMainThread) onThread:_subthread withObject:nil waitUntilDone:NO];
///5.1構建消息  10002 代表 主線程->子線程
NSData *data = [@"你好泰涂,我來自子線程??" dataUsingEncoding:NSUTF8StringEncoding];
CFDataRef msgData = (__bridge_retained CFDataRef)data;
///5.2發(fā)送消息
CFMessagePortSendRequest(_mainPort, 1002, msgData, 0.1, 0.0, NULL, NULL);
CFRelease(msgData);
///6.消息發(fā)送:主線程->子線程
CFStringRef message = CFSTR("你好鲫竞,我來自主線程??");
CFDataRef outData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
///發(fā)送消息, 10001 代表 主線程->子線程
CFMessagePortSendRequest(_subPort, 1001, outData, 0.1, 0.0, NULL, NULL);
///釋放資源
CFRelease(outData);
CFRelease(message);

Timer

CoreFoundationtimer的結構:

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;       /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

蘋果系統(tǒng)中定義的延遲調用API逼蒙,底層便是基于Timer實現(xiàn)的:

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

示例1: 創(chuàng)建子線程開啟一個CoreFoundationtimer:

///1.開啟子線程
  _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程下創(chuàng)建&添加timer
- (void)subthreadOperation {
   ///保存子線程`RL`
    _subRunLoop = CFRunLoopGetCurrent();
    @autoreleasepool {
        __weak typeof(self)weakSelf = self;
        CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
          ///定時器事件
        });
        _cftimer = timer;
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
        CFRunLoopRun();
    }
    ///停止時調用
    NSLog(@"RunLoop:我要走向毀滅从绘,不要攔我呀!??");
}
///3.停止定時器&子線程的RL
- (void)stopCFTimerLoop {
    ///3.1移除`timer`
    CFRunLoopRemoveTimer(_subRunLoop, _cftimer, kCFRunLoopDefaultMode);
    ///3.2停止RL
    CFRunLoopStop(_subRunLoop);
    CFRelease(_cftimer);
}

示例2: 創(chuàng)建子線程開啟一個NSFoundationtimer:

///1.開啟子線程
  _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程下創(chuàng)建&添加timer
- (void)subthreadOperation {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    } else {
        _timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
           ///定時器事件
        }];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
        ///MARK: 定時器停止時是牢,子線程Runloop退出的思考僵井?
        ///[[NSRunLoop currentRunLoop]run];
        ///建議使用這種方式
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"RunLoop:我要走向毀滅,你不要攔我呀驳棱!??");
    }
}
///3.停止定時器&子線程的RL
- (void)stopTimerLoop {
    ///This method is the only way to remove a timer from an NSRunLoop object.
    [_timer invalidate];
    ///停止
    CFRunLoopStop(_subRunLoop);    
}

示例2開啟子線程的RunLoop采用[[NSRunLoop currentRunLoop]run]的方式批什,在不調用[_timer invalidate]的情況下,RunLoop是無法退出的社搅,而runMode:beforeDate:是可以的驻债。這種方式可以保證我們在RunLoop中有其他事件源時并且未移除的情況下,能退出RunLoop形葬。

總結一下就是runMode:beforeDate:在不移除事件的情況下却汉,能顯式退出,而[[NSRunLoop currentRunLoop]run]在不移除事件的情況下荷并,不能顯式退出合砂。

子線程保活

子線程保活翩伪,本質就是開啟子線程的RunLoop微猖。但開啟子線程的RunLoop前,必須要保證RunLoop中至少有個Timer缘屹、Souce0Source1凛剥。

最簡單的保活方式:

///1.開啟子線程
  _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2.子線程下創(chuàng)建&添加timer
- (void)subthreadOperation {
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    _shouldKeepRunning = YES;
    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    } while (_shouldKeepRunning);   
}
///3.停止
- (void)stopLoop {
    CFRunLoopStop(_subRunLoop);  
    _shouldKeepRunning = NO;
}

參考資料

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

https://blog.ibireme.com/2015/05/18/runloop/

https://github.com/apple/darwin-libpthread/blob/main/src/pthread_tsd.c

https://opensource.apple.com/source/CF/CF-1153.18/CFPlatform.c.auto.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末轻姿,一起剝皮案震驚了整個濱河市犁珠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌互亮,老刑警劉巖犁享,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異豹休,居然都是意外死亡炊昆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門威根,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凤巨,“玉大人,你說我怎么就攤上這事洛搀「易拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵留美,是天一觀的道長彰檬。 經常有香客問我,道長独榴,這世上最難降的妖魔是什么僧叉? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮棺榔,結果婚禮上瓶堕,老公的妹妹穿的比我還像新娘。我一直安慰自己症歇,他們只是感情好郎笆,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忘晤,像睡著了一般宛蚓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上设塔,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天凄吏,我揣著相機與錄音,去河邊找鬼。 笑死痕钢,一個胖子當著我的面吹牛图柏,可吹牛的內容都是我干的。 我是一名探鬼主播任连,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼蚤吹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了随抠?” 一聲冷哼從身側響起裁着,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拱她,沒想到半個月后二驰,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡椭懊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年诸蚕,在試婚紗的時候發(fā)現(xiàn)自己被綠了步势。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氧猬。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖坏瘩,靈堂內的尸體忽然破棺而出盅抚,到底是詐尸還是另有隱情,我是刑警寧澤倔矾,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布妄均,位于F島的核電站,受9級特大地震影響哪自,放射性物質發(fā)生泄漏丰包。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一壤巷、第九天 我趴在偏房一處隱蔽的房頂上張望邑彪。 院中可真熱鬧,春花似錦胧华、人聲如沸寄症。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽有巧。三九已至,卻和暖如春悲没,著一層夾襖步出監(jiān)牢的瞬間篮迎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甜橱,地道東北人享言。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像渗鬼,于是被迫代替她去往敵國和親览露。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內容