RunLoop是什么?
RunLoop
是iOS
/Mac OS
开发中比较重要的知识点,它贯穿程序运行的整个过程。它是线程基础架构的一部分,是一种保障线程循环处理事件而不会退出的机制。同时也负责管理线程需要处理的事件,让线程有事儿时忙碌,没事儿时休眠。
每个线程都有一个关联的RunLoop
对象,子线程的RunLoop
是需要手动开启的,主线程的RunLoop
作为应用启动的一部分由系统自动开启。
iOS
/Mac OS
提供了NSRunLoop
和CFRunLoopRef
两个对象,帮助我们配置和管理线程的RunLoop
。CFRunLoopRef
提供纯C
实现并且线程安全的API;NSRunLoop
是基于CFRunLoopRef
封装的面向对象的API,这个API不是线程安全的。
RunLoop与线程的关系
苹果是不建议我们自己创建RunLoop
对象,但是我们可以通过下列方式获取特定线程下的RunLoop
对象:
[NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop];
//CoreFoundation
CFRunLoopGetMain();
CFRunLoopGetCurrent();
CoreFoundation
是开源的(下载地址),我们可以查看CFRunLoopRef
的关于CFRunLoopGetMain
和CFRunLoopGetCurrent
的实现:
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
源码查看可前往此处。
总结:
-
RunLoop
与线程之间是一一对应的 - 当线程需要获取对应的
RunLoop
时,才会创建RunLoop
对象 - 线程销毁的时候会销毁
RunLoop
对象
RunLoop的相关类
CoreFoundation
中与RunLoop
有关的5
个结构体:
CFRunLoopRef //runLoop对象
CFRunLoopModeRef //runLoop运行的模式
CFRunLoopTimerRef// 基于时间的触发器
CFRunLoopSourceRef//事件源,source0:自定义事件输入源 和 source1 :基于mach内核端口的事件源
CFRunLoopObserverRef //用于监听runLoop运行状态的观察者
它们之间的关系如下:
具体可通过打印[NSRunLoop currentRunLoop]
查看,也可通过查看CFRunLoopRef
和CFRunLoopModeRef
的结构定义。两者的结构定义如下:
///CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
//...
CFStringRef _name;
//...
CFMutableSetRef _sources0; //
CFMutableSetRef _sources1;//
CFMutableArrayRef _observers; //
CFMutableArrayRef _timers; //
//...
};
///CFRunLoop.h 类型重命名
typedef struct __CFRunLoop * CFRunLoopRef;
///CFRunLoop.c 结构体
struct __CFRunLoop {
//..
CFMutableSetRef _commonModes; // String UITrackingRunLoopMode/kCFRunLoopDefaultMode
CFMutableSetRef _commonModeItems;// observer/source/timer
CFRunLoopModeRef _currentMode; //当前运行的mode
CFMutableSetRef _modes; //内置的modes;
//...
};
RunLoop的模式
每次运行RunLoop
都需要指定一个Mode
,该Mode
会被设置为_currentMode
,只有与该Mode
关联的输入源source0
、source1
才能被处理,同样的,监听RunLoop
的运行,只有与该Mode
关联的observers
才能收到通知。
///`RunLoop`指定`Mode`运行
CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
程序在运行的过程中,会处理基于时间的、系统的、用户的事件,这些事件在程序运行期间有着不同的优先级,为了满足应用层依据优先级对这些事件的管理,系统采用RunLoopMode
对这些事件分组,然后交由RunLoop
去管理。除了系统定义的默认模式和常用模式,我们也可以自定义模式,但是自定义的模式中必须有关联的事件,否则自定义模式没有任何意义。
_commonModeItems
与_commonModes
是kCFRunLoopCommonModes (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
模式下,最终定时器会被添加到kCFRunLoopDefaultMode
和UITrackingRunLoopMode
下;当然也可以自行添加到这两个模式中。
查看CoreFoundation
中CFRunLoopAddTimer
方法的实现,可以更深入的理解_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`
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
函数启动。
通过程序启动时,函数的调用栈,发现调用了CFRunLoopRunSpecific
;
并且Mode
之间的切换通过LLDB
调试方式:b CFRunLoopRunSpecific
和 b __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
运行时的事件的执行序列,大致如下:
- 通知观察者,
RunLoop
即将进入 - 通知观察者,
RunLoop
即将触发Timer
- 通知观察者
,RunLoop
即将处理Source0
(非mach_port
) - 触发任何准备触发的非基于端口的
Source0
输入源 - 如果基于
mach_port
的输入源Source1
已经就绪等待触发,则跳转第9步处理Source1
. - 通知观察者,
RunLoop
即将进入休眠 - 让线程进入休眠,直到发生一下事件之一:
- 基于
mach_port
的输入源Source1
发生; - 定时器触发;
-
RunLoop
设置的timeout
生效,运行即将结束; -
RunLoop
被显式唤醒,调用CFRunLoopWakeUp
;
- 基于
- 通知观察者,
RunLoop
即将被唤醒 - 唤醒后,处理待处理的事件:
- 用户定义的定时器启动,跳转第2步,处理定时器事件,重新开始循环(
2 ~ 9
) - 处理基于端口的输入源,传递收到的消息。
-
RunLoop
被显式唤醒但还没超时,跳转第2步,重新开始循环(2 ~ 9
)
- 用户定义的定时器启动,跳转第2步,处理定时器事件,重新开始循环(
- 通知观察者,
RunLoop
退出
RunLoop
退出时,运行函数会返回下列枚举值:
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
kCFRunLoopRunFinished = 1,
kCFRunLoopRunStopped = 2,
kCFRunLoopRunTimedOut = 3,
kCFRunLoopRunHandledSource = 4
};
-
Source
处理完毕并且需要立即停止runloop
时,返回kCFRunLoopRunHandledSource
,退出RunLoop
; -
RunLoop
设置的timeout
生效,返回kCFRunLoopRunTimedOut
,退出`RunLoop; - 显式调用
CFRunLoopStop
函数,设置RunLoop
状态为STOPPED
,返回kCFRunLoopRunStopped
,退出RunLoop
; -
RunLoop
运行的Mode
是停止状态,返回kCFRunLoopRunStopped
,退出RunLoop
; -
RunLoop
运行的Mode
为空,没有timer
或source0
或source1
,或者runloop
没有需要执行的block
返回kCFRunLoopRunFinished
,退出RunLoop
;
最后再通过一张图,总结下RunLoop
内部运行逻辑,大致如下:
RunLoop的应用
Oberserver
CoreFoundation
中Observer
的结构:
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
值从小到大的顺序进行事件回调的。
示例: 创建滑动视图并为主线程RunLoop
的kCFRunLoopCommonModes (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
CoreFoundation
中Source
的结构:
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 *)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 *)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
之上标准化的API
,Mach
才是核心,负责线程与进程管理、虚拟内存管理、进程通信与消息传递、任务调度等。
基于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_port
的Source1
实现进程通信。
示例: 创建一个子线程,通过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
CoreFoundation
中timer
的结构:
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 *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
示例1: 创建子线程开启一个CoreFoundation
的timer
:
///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: 创建子线程开启一个NSFoundation
的timer
:
///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
中至少有个Timer
、Souce0
或Source1
。
最简单的保活方式:
///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