美文网首页
谈谈 iOS RunLoop 底层

谈谈 iOS RunLoop 底层

作者: QiShare | 来源:发表于2022-02-24 14:29 被阅读0次

    RunLoop是什么?

    RunLoopiOS/Mac OS开发中比较重要的知识点,它贯穿程序运行的整个过程。它是线程基础架构的一部分,是一种保障线程循环处理事件而不会退出的机制。同时也负责管理线程需要处理的事件,让线程有事儿时忙碌,没事儿时休眠。

    每个线程都有一个关联的RunLoop对象,子线程的RunLoop是需要手动开启的,主线程的RunLoop作为应用启动的一部分由系统自动开启。

    iOS/Mac OS提供了NSRunLoopCFRunLoopRef两个对象,帮助我们配置和管理线程的RunLoopCFRunLoopRef提供纯C实现并且线程安全的API;NSRunLoop是基于CFRunLoopRef封装的面向对象的API,这个API不是线程安全的。

    RunLoop与线程的关系

    苹果是不建议我们自己创建RunLoop对象,但是我们可以通过下列方式获取特定线程下的RunLoop对象:

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

    CoreFoundation是开源的(下载地址),我们可以查看CFRunLoopRef的关于CFRunLoopGetMainCFRunLoopGetCurrent的实现:

    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);
            /// 创建局部变量`Dictionary`
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            /// 【创建主线程的`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) {
            /// 创建`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`,
            ///此举目的是为了:当线程销毁时,实现对`RunLoop`的销毁
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
           /// 如何实现的呢?请继续往下看一探究竟!
            }
        }
        return loop;
    }
    
    ///上述代码的`_CFGetTSD`与`_CFSetTSD`的实现如下:
    ///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;
        ///析构函数关联
        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`,并关联析构函数
            /// `CF_TSD_KEY` = 55 ,在不同线程该`Key`值可以共享,但此`Key`对应的值却是不同的。
            ///每个线程都会有自己的`tsd`,它们共用`CF_TSD_KEY`这个`key`
            /// 线程销毁时苹果系统会调用:
            /// `_pthread_exit` -> `_pthread_tsd_cleanup` -> 
            ///`_pthread_tsd_cleanup_new`->`_pthread_tsd_cleanup_key`
            ///当线程销毁时,会调用关联的析构函数`__CFTSDFinalize`,保证线程对应的私有数据也能销毁 
            ///具体可参照函数: _pthread_tsd_cleanup_key
            /// 函数实现[细节](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时,才会创建RunLoop对象
    3. 线程销毁的时候会销毁RunLoop对象

    RunLoop的相关类

    CoreFoundation中与RunLoop有关的5个结构体:

    CFRunLoopRef //runLoop对象
    CFRunLoopModeRef //runLoop运行的模式
    CFRunLoopTimerRef// 基于时间的触发器
    CFRunLoopSourceRef//事件源,source0:自定义事件输入源 和 source1 :基于mach内核端口的事件源
    CFRunLoopObserverRef //用于监听runLoop运行状态的观察者
    

    它们之间的关系如下:

    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关联的输入源source0source1才能被处理,同样的,监听RunLoop的运行,只有与该Mode关联的observers才能收到通知。

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

    程序在运行的过程中,会处理基于时间的、系统的、用户的事件,这些事件在程序运行期间有着不同的优先级,为了满足应用层依据优先级对这些事件的管理,系统采用RunLoopMode对这些事件分组,然后交由RunLoop去管理。除了系统定义的默认模式和常用模式,我们也可以自定义模式,但是自定义的模式中必须有关联的事件,否则自定义模式没有任何意义。

    _commonModeItems_commonModeskCFRunLoopCommonModes (NSRunLoopCommonModes)背后的实现逻辑,可以理解为采用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默认包含这两种模式;
    开发中会遇到在主线程启动一个定时器时,会受视图滑动的影响的问题,解决办法:

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

    将定时器添加到NSRunLoop对象的NSRunLoopCommonModes模式下,最终定时器会被添加到kCFRunLoopDefaultModeUITrackingRunLoopMode下;当然也可以自行添加到这两个模式中。

    查看CoreFoundationCFRunLoopAddTimer方法的实现,可以更深入的理解_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) {
                ///创建`_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是否有,找不到创建
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if (NULL != rlm) {
                if (NULL == rlm->_timers) { ///创建存放`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) {
                     //...
                    //定时器已关联的runloop与当前runloop不一致,返回
            return;
            }
                ///定时器的modes 添加该mode的名称
            CFSetAddValue(rlt->_rlModes, rlm->_name);
                //采用mktimer(mach kernel timer)通过machport 和 machmsg 触发定时器事件
                __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

    通过程序启动时,函数的调用栈,发现调用了CFRunLoopRunSpecific
    并且Mode之间的切换通过LLDB调试方式:b CFRunLoopRunSpecificb __CFRunLoopRun,发现也会调用到该方法,源码分析如下:

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
        ///进程检查
        CHECK_FOR_FORK();
        ///是否销毁
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        ///互斥锁
        __CFRunLoopLock(rl);
        //从`runloop`的`modes`找到`modeName`对应的`mode`,找不到也不创建
        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的状态
        volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
        CFRunLoopModeRef previousMode = rl->_currentMode;
        rl->_currentMode = currentMode;
        int32_t result = kCFRunLoopRunFinished;
        ///`CurrentMode`的状态为`kCFRunLoopEntry`时,
        ///通过`__CFRunLoopDoObservers`通知当前`Mode`对应的观察者
        /// _observerMask 设置的是rlo需要监听的状态
        ///1.runloop处于`kCFRunLoopEntry`,通知runloopmode->observers,runloop即将进入
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
            ///`RunLoop`真正的运行逻辑
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
             ///`CurrentMode`的状态为`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`是否已经停止,停止则执行`return kCFRunLoopRunStopped`。
        ///...
        ///声明 mach_port,当(主线程的消息分发队列是安全的&当前rl是主线程的rl&rlm->name in  rl-> commonModes),存放与主线程(主队列)的runLoop关联的`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系统下,设置与Mode关联的队列对应的端口号(定时器队列)
    #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死循环 当retVal != 0 时 停止循环
        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即将触发timer回调
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            ///3.runloop处于`kCFRunLoopBeforeSources`,通知runloopmode->observers,runloop即将触发Source0(非mach_port)回调
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            ///执行runloop通过`struct _block_item *_blocks_head、_blocks_tail`加入runloop的block; 
            ///最终调用`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__`
        __CFRunLoopDoBlocks(rl, rlm);
            /// 4. 执行自定义的`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处理完毕,再次执行被加入的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(;;)`循环,等待,
                ///通过`mach_msg`等待从rl的`dispatchPort`获取信息,如果成功获取,则跳转处理source1。
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    ///处理msg
                    goto handle_msg;
                }
                ///....
            }
            didDispatchPortLastTime = false;
            ///如果rl没有退出 && 处于`kCFRunLoopBeforeWaiting`状态
            ///6.runloop处于`kCFRunLoopBeforeWaiting`,通知runloopmode->observers,runloop即将进入休眠(sleep)
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            ///设置runloop的状态为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`执行`if(TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } 
            ///让线程进入休眠,等待被`mach_msg`函数唤醒
            msg = (mach_msg_header_t *)msg_buffer;
            ///参数超时时间为`TIMEOUT_INFINITY`触发rl的sleep,(rlm的portSet) poll = false 标识rl 未停止,未超时 waitSet 还有定时器port
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            ///macOS会循环执行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状态
        __CFRunLoopUnsetSleeping(rl);
            ///如果rl没有退出 && 处于`kCFRunLoopAfterWaiting`状态 
            ///8.runloop处于`kCFRunLoopAfterWaiting`,通知runloopmode->observers,runloop即将被唤醒
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            ///收到来自mach_port的消息会跳转此处执行
            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();
                ///如果未处理,重设下次触发时间
                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,如果需要回复消息,则执行消息回复
                (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
            ////执行一下加入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状态为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需要执行,并且不是主队列
            retVal = kCFRunLoopRunFinished;
        }
            //...
        } while (0 == retVal);
         ///...
        return retVal;
    }
    
    

    RunLoop运行函数内部是一个do-while循环,让线程持续运行,接收事件,处理事件;RunLoop定义了一些状态,当它在特定RunLoopMode下运行时,可以向该Mode下注册的观察者发送消息;

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),///进入RunLoop
        kCFRunLoopBeforeTimers = (1UL << 1),///RunLoop即将触发定时器事件
        kCFRunLoopBeforeSources = (1UL << 2),///RunLoop即将处理Source事件
        kCFRunLoopBeforeWaiting = (1UL << 5),///RunLoop即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6),///RunLoop即将被唤醒,但尚未开始处理唤醒它的事件
        kCFRunLoopExit = (1UL << 7),///退出RunLoop
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    苹果文档中总结了RunLoop运行时的事件的执行序列,大致如下:

    1. 通知观察者,RunLoop即将进入
    2. 通知观察者,RunLoop即将触发Timer
    3. 通知观察者,RunLoop即将处理Source0(非mach_port
    4. 触发任何准备触发的非基于端口的Source0输入源
    5. 如果基于mach_port的输入源Source1已经就绪等待触发,则跳转第9步处理Source1.
    6. 通知观察者,RunLoop即将进入休眠
    7. 让线程进入休眠,直到发生以下事件之一:
      • 基于mach_port的输入源Source1发生;
      • 定时器触发;
      • RunLoop设置的timeout生效,运行即将结束;
      • RunLoop被显式唤醒,调用CFRunLoopWakeUp;
    8. 通知观察者,RunLoop即将被唤醒
    9. 唤醒后,处理待处理的事件:
      • 用户定义的定时器启动,跳转第2步,处理定时器事件,重新开始循环(2 ~ 9
      • 处理基于端口的输入源,传递收到的消息。
      • RunLoop被显式唤醒但还没超时,跳转第2步,重新开始循环(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状态为STOPPED,返回kCFRunLoopRunStopped,退出RunLoop;
    4. RunLoop运行的Mode是停止状态,返回kCFRunLoopRunStopped,退出RunLoop;
    5. RunLoop运行的Mode为空,没有timersource0source1,或者runloop没有需要执行的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哪些状态
        CFIndex _order;///状态事件触发时,依次通知的观察者,值越小优先级越高 
        CFRunLoopObserverCallBack _callout; //状态事件触发时的回调
        CFRunLoopObserverContext _context;// 上下文    
    };
    typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
    

    观察者,可以被添加到RunLoop的多个Mode下,同一个Mode下可以有多个Oberserver,当事件触发时,观察者是依据_order值从小到大的顺序进行事件回调的。

    示例: 创建滑动视图并为主线程RunLoopkCFRunLoopCommonModes (NSRunLoopCommonModes)添加Observer,观察RunLoop的切换。

    ///设置观察者
    - (void)addObserverForMainRunLoop {
        /*
         UITrackingRunLoopMode,GSEventReceiveRunLoopMode,
         kCFRunLoopDefaultMode,kCFRunLoopCommonModes
         */
        void *info = (__bridge_retained void *)self;
        CFRunLoopObserverContext context = {0,info,NULL,NULL,NULL};
       //一个优先级索引,指示处理运行循环观察者的顺序。在给定的运行循环模式下,当多个运行循环观察者被调度在同一活动阶段时,观察者按此参数的递增顺序进行处理。传递 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;
    

    苹果系统定义了一些API,底层是基于Source0实现的:

    ///子线程->主线程
    - (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从主线程发送消息给子线程。

    ///1.开启子线程
     _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
    ///2.子线程的方法中,添加一个`Souce0`事件,并开启`RL`
    - (void)subthreadOperation {
        ///3.保存子线程的runloop
        _subRunLoop = CFRunLoopGetCurrent();
        ///4.创建&添加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.主线程触发Source0事件
    - (void)buttonAction:(id)sender {
        //5.1 触发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只不过是被包装的带有上下文的函数,需要主动触发,这个函数才会被执行。

    Source1

    Sorce1是基于mach_port的事件,它是内核事件,苹果系统的内核是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)实现事件传递。陷阱最终的用途,是在用户程序和内核之间提供一个像过程一样的接口,称为系统调用

    macOS系统中,可以使用基于mach_portSource1实现进程通信。

    示例: 创建一个子线程,通过Source1建立主线程与子线程信道,实现双向通信。

    ///1.开启子线程
     _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(launchThreadWithPort:) object:nil];
    ///2.子线程的方法中,添加一个`Souce1`事件,并开启`RL`
    - (void)launchThreadWithPort:(NSPort*)port {
        @autoreleasepool {
            ///3.创建&添加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.消息发送:子线程->主线程
    [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发送消息
    CFMessagePortSendRequest(_mainPort, 1002, msgData, 0.1, 0.0, NULL, NULL);
    CFRelease(msgData);
    ///6.消息发送:主线程->子线程
    CFStringRef message = CFSTR("你好,我来自主线程🏡");
    CFDataRef outData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
    ///发送消息, 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;
    

    苹果系统中定义的延迟调用API,底层便是基于Timer实现的:

    - (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: 创建子线程开启一个CoreFoundationtimer:

    ///1.开启子线程
      _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
    ///2.子线程下创建&添加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: 创建子线程开启一个NSFoundationtimer:

    ///1.开启子线程
      _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
    ///2.子线程下创建&添加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中至少有个TimerSouce0Source1

    最简单的保活方式:

    ///1.开启子线程
      _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
    ///2.子线程下创建&添加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

    相关文章

      网友评论

          本文标题:谈谈 iOS RunLoop 底层

          本文链接:https://www.haomeiwen.com/subject/ievzlrtx.html