谈谈RunLoop底层

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容