# 监听 RunLoop ```Objective-C //给 RunLoop 添加监听者 - (void)testRunLoopObserver{ //创建监听者 // CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopAllActivities, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>) /* 创建监听对象 参数1:分配内存空间 参数2:要监听的状态 kCFRunLoopAllActivities :所有状态 参数3:是否要持续监听 参数4:优先级 参数5:回调 */ CFRunLoopObserverRef oberver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"RunLoop 闪亮登场"); break; case kCFRunLoopBeforeTimers: NSLog(@"RunLoop 大哥要处理 Timer 了"); break; case kCFRunLoopBeforeSources: //Source 有2种。Source0:非基于 port 的,用户主动触发的事件。Source1:基于 port,通过内核和其它线程互相发送消息 NSLog(@"RunLoop 大哥要处理 Source 了"); break; case kCFRunLoopBeforeWaiting: NSLog(@"RunLoop 大哥没事干要睡觉了"); break; case kCFRunLoopAfterWaiting: NSLog(@""); NSLog(@"RunLoop 大哥终于等到有缘人了,要醒来开始干活了"); break; case kCFRunLoopExit: NSLog(@"RunLoop 大哥要退出离开了"); break; default: break; } }); /* 参数1:要监听哪个RunLoop 参数2:监听者 参数3:要监听 RunLoop 在哪种运行模式下的状态 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), oberver, kCFRunLoopDefaultMode); //CoreFoundation 的内存管理:凡是带有 Create、Copy、Retain等字眼的函数创建出来的对象都需要在最后调用 release CFRelease(oberver); [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(wakeupRunLoop) userInfo:nil repeats:YES]; } //等到 RunLoop 休眠后,5秒钟叫醒 RunLoop - (void)wakeupRunLoop{ NSLog(@"%s",__func__); } /* 2018-08-01 11:23:49.401626+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:49.401950+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:49.402326+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:49.402509+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:49.402721+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:49.402855+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:49.403080+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了 2018-08-01 11:23:49.459238+0800 RunLoop[38148:1994974] 2018-08-01 11:23:49.459512+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了 2018-08-01 11:23:49.459740+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:49.459932+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:49.460431+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:49.460607+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:49.460775+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了 2018-08-01 11:23:49.880631+0800 RunLoop[38148:1994974] 2018-08-01 11:23:49.880867+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了 2018-08-01 11:23:49.881530+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:49.881699+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:49.881870+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了 2018-08-01 11:23:54.402263+0800 RunLoop[38148:1994974] 2018-08-01 11:23:54.402562+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了 2018-08-01 11:23:54.402773+0800 RunLoop[38148:1994974] -[ViewController wakeupRunLoop] 2018-08-01 11:23:54.403081+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:54.403245+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:54.403476+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了 2018-08-01 11:23:59.402151+0800 RunLoop[38148:1994974] 2018-08-01 11:23:59.402511+0800 RunLoop[38148:1994974] RunLoop 大哥终于等到有缘人了,要醒来开始干活了 2018-08-01 11:23:59.402687+0800 RunLoop[38148:1994974] -[ViewController wakeupRunLoop] 2018-08-01 11:23:59.402913+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Timer 了 2018-08-01 11:23:59.403037+0800 RunLoop[38148:1994974] RunLoop 大哥要处理 Source 了 2018-08-01 11:23:59.403156+0800 RunLoop[38148:1994974] RunLoop 大哥没事干要睡觉了 ``` ![RunLoop 运行原理图1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180801-104553@2x.png) 上个实验是在主线程对 RunLoop 进行的监听,但是由于是主线程是由系统创建的,所以系统也创建了对应的主 RunLoop,所以我们看不到 RunLoop 创建的状态,为了模拟完整的状态,我们开启子线程,在子线程中模拟 ```objective-c - (void)testRunLoopObserverOnSubThread{ //创建并发队列 dispatch_queue_t queue = dispatch_queue_create("com.lbp.testRunLoopOnSubThread", DISPATCH_QUEUE_CONCURRENT); //开启子线程 dispatch_async(queue, ^{ //1、获得当前线程下的 RunLoop CFRunLoopRef runloop = CFRunLoopGetCurrent(); //2、为 RunLoop 创建观察者 CFRunLoopObserverRef obersver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"RunLoop 闪亮登场"); break; case kCFRunLoopBeforeTimers: NSLog(@"RunLoop 大哥要处理 Timer 了"); break; case kCFRunLoopBeforeSources: //Source 有2种。Source0:非基于 port 的,用户主动触发的事件。Source1:基于 port,通过内核和其它线程互相发送消息 NSLog(@"RunLoop 大哥要处理 Source 了"); break; case kCFRunLoopBeforeWaiting: NSLog(@"RunLoop 大哥没事干要睡觉了"); break; case kCFRunLoopAfterWaiting: NSLog(@""); NSLog(@"RunLoop 大哥终于等到有缘人了,要醒来开始干活了"); break; case kCFRunLoopExit: NSLog(@"RunLoop 大哥要退出离开了"); break; default: break; } }); //为了运行 RunLoop 必须触发事件 [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(wakeUpRunLoopOnSubThread) userInfo:nil repeats:NO]; //3、为当前的 RunLoop 添加观察者 CFRunLoopAddObserver(runloop, obersver, kCFRunLoopDefaultMode); //4、在 CoreFoundation 框架中, create、copy、retain 过的对象都必须在最后 release CFRelease(obersver); //5、在非主线程创建的 RunLoop 必须触发运行 [[NSRunLoop currentRunLoop] run]; }); } - (void)wakeUpRunLoopOnSubThread{ NSLog(@"%s",__func__); } /* 2018-08-01 14:23:06.453282+0800 RunLoop[2376:115968] RunLoop 闪亮登场 2018-08-01 14:23:06.453608+0800 RunLoop[2376:115968] RunLoop 大哥要处理 Timer 了 2018-08-01 14:23:06.453781+0800 RunLoop[2376:115968] RunLoop 大哥要处理 Source 了 2018-08-01 14:23:06.453982+0800 RunLoop[2376:115968] RunLoop 大哥没事干要睡觉了 2018-08-01 14:23:08.458237+0800 RunLoop[2376:115968] 2018-08-01 14:23:08.458658+0800 RunLoop[2376:115968] RunLoop 大哥终于等到有缘人了,要醒来开始干活了 2018-08-01 14:23:08.458894+0800 RunLoop[2376:115968] -[ViewController wakeUpRunLoopOnSubThread] 2018-08-01 14:23:08.459082+0800 RunLoop[2376:115968] RunLoop 大哥要退出离开了 */ ``` ## RunLoop 内部运行原理 ![RunLoop 运行原理图1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/image-20180801113342611.png) - 图上左上角的 Input source 是早期 RunLoop 的分法,现在分法为:Source0 和 Source1。 - Source0:非基于 port 的,用户主动触发的事件。 - Source1:基于 port,通过内核和其它线程互相发送消息 - RunLoop 我们不能自己手动创建,而是可以通过 [NSRunLoop currentRunLoop] 方法获取,类似于懒加载。系统底层的做法是在全局维护了一个字典,字典的 key 和 value 分别是当前的线程和线程对应的 RunLoop,如果新开辟的线程没有对应的 RunLoop,系统则为其创建 RunLoop,并将其写入字典(线程、为其创建的 RunLoop) 运行流程图 ![运行流程](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/4.png) 运行流程说明 ![流程说明](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/3.png) ## RunLoopMode 的概念 ![RunLoop 运行原理图1](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/1785352-087fd4b664e0e387.png) ## 底层实现 内部就是 do-while 的循环,在这个循环内部不断处理各种任务(Timer、Source、Observer) 我们来看看苹果官方开源的 [CFRunLoop.c 文件](https://legacy.gitbook.com/book/fantasticlbp/knowledge-kit/edit#)。看几个关键函数的实现猜测下 RunLoop 的内部原理 以下的代码都有注释说明 **__CFRunLoopModeIsEmpty** 此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer,只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop ```objective-c // expects rl and rlm locked static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) { CHECK_FOR_FORK(); if (NULL == rlm) return true; #if DEPLOYMENT_TARGET_WINDOWS if (0 != rlm->_msgQMask) return false; #endif 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)) return false; // represents the libdispatch main queue // 判断时候有没有_sources0 if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false; // 判断时候有没有_sources1 if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false; // 判断时候有没有_timers if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false; struct _block_item *item = rl->_blocks_head; while (item) { struct _block_item *curr = item; item = item->_next; Boolean doit = false; if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) { doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name)); } else { doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name)); } if (doit) return false; } return true; } ``` **CFRunLoopRun、CFRunLoopRunInMode** 1、2个函数的作用分别是让 RunLoop 跑在 KCFRunLoopDefaultMode 下和特定的 Mode 下 2、2个函数本质上都是调用 CFRunLoopRunSpecific ```objective-c // 用DefaultMode启动 void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } // 用指定的Mode启动,允许设置RunLoop超时时间 SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } ``` **CFRunLoopRunSpecific** 参数1: RunLoop 对象。参数2:运行 Mode 名称。参数3:超时时间。参数4:主_CFRunLoopRun 会用到 ```objective-c // RunLoop的实现 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); // 根据modeName找到对应mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); // 判断mode里没有source/timer, 没有直接返回。 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 *previousPerRun = __CFRunLoopPushPerRunData(rl); CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished; if (currentMode->_observerMask & kCFRunLoopEntry ) // 1. 通知 Observers: RunLoop 即将进入 loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 进入loop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); if (currentMode->_observerMask & kCFRunLoopExit ) // 10.通知 Observers: RunLoop 即将退出。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; } ``` **__CFRunLoopDoObserver** 调用 Observer 回调 联想给 RunLoop 添加观察者,监听 RunLoop 状态。 ```objective-c static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) { /* DOES CALLOUT */ CHECK_FOR_FORK(); CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0; if (cnt < 1) return; /* Fire the observers */ STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1); CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef)); CFIndex obs_cnt = 0; //遍历 rlm-> _observers,将元素放到 collectedObservers 数组中 for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx); if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) { collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo); } } __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); for (CFIndex idx = 0; idx < obs_cnt; idx++) { CFRunLoopObserverRef rlo = collectedObservers[idx]; __CFRunLoopObserverLock(rlo); if (__CFIsValid(rlo)) { Boolean doInvalidate = !__CFRunLoopObserverRepeats(rlo); __CFRunLoopObserverSetFiring(rlo); __CFRunLoopObserverUnlock(rlo); __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info); if (doInvalidate) { CFRunLoopObserverInvalidate(rlo); } __CFRunLoopObserverUnsetFiring(rlo); } else { __CFRunLoopObserverUnlock(rlo); } CFRelease(rlo); } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); if (collectedObservers != buffer) free(collectedObservers); } ``` **__CFRunLoopRun** ```objective-c /* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; return kCFRunLoopRunStopped; } mach_port_name_t dispatchPort = MACH_PORT_NULL; Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ))); if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF(); #if USE_DISPATCH_SOURCE_FOR_TIMERS mach_port_name_t modeQueuePort = MACH_PORT_NULL; if (rlm->_queue) { modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue); if (!modeQueuePort) { CRASH("Unable to get port for run loop mode queue (%d)", -1); } } #endif dispatch_source_t timeout_timer = NULL; struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context)); if (seconds <= 0.0) { // instant timeout seconds = 0.0; timeout_context->termTSR = 0ULL; } else if (seconds <= TIMER_INTERVAL_LIMIT) { dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground(); timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_retain(timeout_timer); timeout_context->ds = timeout_timer; timeout_context->rl = (CFRunLoopRef)CFRetain(rl); timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds); dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout); dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel); uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL); dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL); dispatch_resume(timeout_timer); } else { // 设置RunLoop超时时间 seconds = 9999999999.0; timeout_context->termTSR = UINT64_MAX; } Boolean didDispatchPortLastTime = true; int32_t retVal = 0; do { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED; voucher_t voucherCopy = NULL; #endif uint8_t msg_buffer[3 * 1024]; #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t *msg = NULL; mach_port_t livePort = MACH_PORT_NULL; #elif DEPLOYMENT_TARGET_WINDOWS HANDLE livePort = NULL; Boolean windowsMessageReceived = false; #endif __CFPortSet waitSet = rlm->_portSet; __CFRunLoopUnsetIgnoreWakeUps(rl); if (rlm->_observerMask & kCFRunLoopBeforeTimers) // 2. 通知 Observers: RunLoop 即将触发 Timer 回调 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); if (rlm->_observerMask & kCFRunLoopBeforeSources) // 3. 通知 Observers: RunLoop 即将触发 Source 回调 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 执行被加入的block __CFRunLoopDoBlocks(rl, rlm); // 4. RunLoop 触发 Source0 (非port) 回调 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { // 执行被加入的block __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); // 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息 if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { goto handle_msg; } #elif DEPLOYMENT_TARGET_WINDOWS if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { goto handle_msg; } #endif } didDispatchPortLastTime = false; // 通知 Observers: RunLoop 的线程即将进入休眠(sleep) if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // do not do any user callouts after this point (after notifying of sleeping) // Must push the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don't // want these ports to get serviced. __CFPortSetInsert(dispatchPort, waitSet); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent(); #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS do { if (kCFUseCollectableAllocator) { // objc_clear_stack(0); // memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer. while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue)); if (rlm->_timerFired) { // Leave livePort as the queue port, and service timers below rlm->_timerFired = false; break; } else { if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); } } else { // Go ahead and leave the inner loop. break; } } while (1); #else if (kCFUseCollectableAllocator) { // objc_clear_stack(0); // memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); #endif #elif DEPLOYMENT_TARGET_WINDOWS // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages. __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived); #endif __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart)); // Must remove the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don't // want these ports to get serviced. Also, we don't want them left // in there if this function returns. __CFPortSetRemove(dispatchPort, waitSet); __CFRunLoopSetIgnoreWakeUps(rl); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // 处理消息 handle_msg:; __CFRunLoopSetIgnoreWakeUps(rl); #if DEPLOYMENT_TARGET_WINDOWS if (windowsMessageReceived) { // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); if (rlm->_msgPump) { rlm->_msgPump(); } else { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) { TranslateMessage(&msg); DispatchMessage(&msg); } } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later. // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling. __CFRunLoopSetSleeping(rl); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); __CFRunLoopUnsetSleeping(rl); // If we have a new live port then it will be handled below as normal } #endif if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING(); // handle nothing } else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP(); // do nothing on Mac OS #if DEPLOYMENT_TARGET_WINDOWS // Always reset the wake up port, or risk spinning forever ResetEvent(rl->_wakeUpPort); #endif } #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 #if USE_MK_TIMER_TOO // 9.1 如果一个 Timer 到时间了,触发这个Timer的回调 else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled. // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754 if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer __CFArmNextTimerInMode(rlm, rl); } } #endif // 9.2 如果有dispatch到main_queue的block,执行block else if (livePort == dispatchPort) { CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL); #if DEPLOYMENT_TARGET_WINDOWS void *msg = 0; #endif /**/ __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; didDispatchPortLastTime = true; } // 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件 else { CFRUNLOOP_WAKEUP_FOR_SOURCE(); // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again. voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release); /**/ 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; sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; if (NULL != reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); } #elif DEPLOYMENT_TARGET_WINDOWS sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop; #endif } // Restore the previous voucher _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release); } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); #endif // 执行加入到Loop的block __CFRunLoopDoBlocks(rl, rlm); if (sourceHandledThisLoop && stopAfterHandle) { // 进入loop时参数说处理完事件就返回 retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { // 超出传入参数标记的超时时间了 retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); // 被外部调用者强制停止了 retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // source/timer一个都没有 retVal = kCFRunLoopRunFinished; } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_revert(voucherState); os_release(voucherCopy); #endif // 如果没超时,mode里没空,loop也没被停止,那继续loop } while (0 == retVal); if (timeout_timer) { dispatch_source_cancel(timeout_timer); dispatch_release(timeout_timer); } else { free(timeout_context); } return retVal; } ``` ![image-20200812221231993](/Users/lbp/Desktop/resume/2020-08-12-RunLoopStructure1.png) - performSelector 是在 source0 上实现的 - RunLoopTimer 外部接口设置的精度,精度大于0,则使用 dispatch_source_set_timer,精度小于0,则使用 mk_timer_arm - timer_source 使用 dispatch_timer_STRICT 创建,则系统会尽最大努力遵守设置的 leeway 值 - NSTimer 不准的原因:底层 RunLoop Timer 底层使用的 timer 的精度不高(mk_timer);与 RunLoop 底层的调用机制有关系 - 那么为什么存在 RunLoopTimer?意义是什么?应用场景 ![image-20200812125534225](/Users/lbp/Desktop/Github/knowledge-kit/assets/2020-08-12-RunLoopStructure.png) ```c++ // Data structure to hold TSD data, cleanup functions for each typedef struct __CFTSDTable { uint32_t destructorCount; uintptr_t data[CF_TSD_MAX_SLOTS]; tsdDestructor destructors[CF_TSD_MAX_SLOTS]; } __CFTSDTable; ``` ```c++ // 主线程 RunLoop CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); // 局部静态变量 static CFRunLoopRef __main = NULL; // no retain needed // 创建主线程对应的 RunLoop if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; } // 子线程 RunLoop // 先从 __CFTSDTable 获取 RunLoop,如果有则 return,没有则调用 _CFRunLoopGet0,_CFRunLoopGet0 内部调用 __CFRunLoopCreate 创建 CFRunLoop,然后写入 __CFTSDTable CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); // __CFTSDTable CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; // 没有则创建 return _CFRunLoopGet0(pthread_self()); } CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) { if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (!__CFRunLoops) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); } CFRunLoopRef newLoop = NULL; CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { newLoop = __CFRunLoopCreate(t); CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __CFUnlock(&loopsLock); // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it if (newLoop) { CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { #if _POSIX_THREADS _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); #else _CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop); #endif } } return loop; } ``` - 为什么只有当 RunLoop 中存在 Timer Sourcrs、Input Sources 时,才能保证 RunLoop 不退出? RunLoop 本质就是一个有条件的 do...while 循环。__CFRunLoopModeIsEmpty 里面去判断 source0、source1、timers 不存在则 while 循环条件不满足,RunLoop 退出 ```c++ void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } // expects rl and rlm locked static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) { // ... if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false; if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false; if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false; // ... return true; } SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ // ... CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { Boolean did = false; if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; } // ... result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // ... return result; } ``` ## 线程保活技术: ```objective-c - (void)viewDidLoad { [super viewDidLoad]; [self testKeepAliveThread]; } - (void)testKeepAliveThread { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ self.thread = (ThreadKeepLive *)[[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil]; [self.thread setName:@"LongLiveThread"]; [self.thread setQualityOfService:[NSThread mainThread].qualityOfService]; [self.thread start]; }); } - (void)threadTask { [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"come"); }]; [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; while (true) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } } ``` - come 会打印几次?1次,因为 RunLoop 跑起来之后,后面的代码不会执行,内部不断的 do...while - NSRunLoop 常驻线程可以运行在 NSRunLoopCommonModes 下吗? 不可以。因为在跑的时候如果 modeName 等于 kCFRunLoopCommonModes 则直接 kCFRunLoopRunFinished,则 RunLoop 的 while 循环条件失败 ```objective-c SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName); _CFRunLoopError_RunCalledWithInvalidMode(); }); return kCFRunLoopRunFinished; } // ... return result; } ``` ### Mach Port 跨线程通信 1. Mach IPC 基于 Mach 内核实现进程间通讯。 2. Mach IPC 被抽象为3种操作:messages、ports、and port sets 3. Mach port 跨线程通信 线程 B 有个 port 在等待消息,具有消息接收权限,等待消息到来的时候会阻塞当前线程 线程 A 有个 port 在发送消息,具有发送消息权限,要把发送的消息包装成消息,通过消息队列传递,message 包括:header(目的地 port、size)、data。 线程 B 收到消息后,解除 block,线程继续向下运行 ![image-20200813024843611](/Users/lbp/Library/Application Support/typora-user-images/image-20200813024843611.png) 4. Mach port 如何进行跨线程通信? 线程开启一个 port,然后给 port 申请接收、发送的权限。mach_msg 是通信函数,在等待消息的时候不加 timeout 则会一直阻塞,等到消息到来 ![image-20200813093820911](/Users/lbp/Desktop/Github/knowledge-kit/assets/2020-08-13-MachPort.png) 5. 在测试工作中 main.m 文件中打印当前的 RunLoop ```objective-c int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { appDelegateClassName = NSStringFromClass([AppDelegate class]); } NSLog(@"%@", NSRunLoop.currentRunLoop); return UIApplicationMain(argc, argv, nil, appDelegateClassName); } // 2020-08-13 09:49:41.326621+0800 ***[52423:2383402] {wakeup port = 0x1103, stopped = false, ignoreWakeUps = true, current mode = (none), common modes = {type = mutable set, count = 1, entries => 2 : {contents = "kCFRunLoopDefaultMode"} } , common mode items = (null), modes = {type = mutable set, count = 1, entries => 2 : {name = kCFRunLoopDefaultMode, port set = 0x1003, queue = 0x6000007f0100, source = 0x6000007f0280 (not fired), timer port = 0xe03, sources0 = (null), sources1 = (null), observers = (null), timers = (null), currently 618976181 (257342842892563) / soft deadline in: 1.84464867e+10 sec (@ -1) / hard deadline in: 1.84464867e+10 sec (@ -1) }, } } ``` ![Mach Port Test Demo](./../assets/2020-08-13-MachPortTest.png) 可以看到 main.m 中,还没有 return 的时候当前 RunLoop 的内部结构中存在一个 **wakeup port** 的端口。查看 RunLoop 源代码 **wakeup port** 就是 mach port 的一种。 ```c++ typedef mach_port_t __CFPort; struct __CFRunLoop { CFRuntimeBase _base; _CFRecursiveMutex _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop _CFThreadRef _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart; _Atomic(uint8_t) _fromTSD; CFLock_t _timerTSRLock; }; ``` ```c++ void CFRunLoopWakeUp(CFRunLoopRef rl) { // ... kern_return_t ret; /* We unconditionally try to send the message, since we don't want * to lose a wakeup, but the send may fail if there is already a * wakeup pending, since the queue length is 1. */ ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0); // ... int ret; do { ret = eventfd_write(rl->_wakeUpPort, 1); } while (ret == -1 && errno == EINTR); // ... SetEvent(rl->_wakeUpPort); // ... } ``` `CFRunLoopWakeUp(CFRunLoopGetCurrent());` 可以唤醒 RunLoop,函数的底层实现如上。核心实现就是 `__CFSendTrivialMachMessage` 函数。在iOS 中,除了 source1 可以自己唤醒 RunLoop 之外,其他的事件都需要用户手动唤醒 RunLoop 才可以。RunLoop 提供了专门的方法来实现这个功能。其核心部分就是调用 mach_msg 来向指定的 **_wakeUpPort** 端口发送消息,从而唤醒线程继续工作。 为什么 Source1 可以唤醒 RunLoop?因为 Source1 本质上就是针对 Mach Port 的封装 6. 做个实验检测 _wakeUpPort 端口 ```objective-c - (void)viewDidLoad { [super viewDidLoad]; [self listenWakeUpPort]; } - (void)listenWakeUpPort { NSArray *array = [NSRunLoop.currentRunLoop.description componentsSeparatedByString:@"wakeup port = "]; NSString *wakeupPort = [array.lastObject substringToIndex:[array.lastObject rangeOfString:@","].location]; dispatch_queue_t queue = dispatch_queue_create("com.test.wake_up_port_queue", DISPATCH_QUEUE_CONCURRENT); dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, [self numberWithHexString:wakeupPort], 0, queue); dispatch_source_set_event_handler(source, ^{ mach_port_t port = (mach_port_t)dispatch_source_get_handle(source); NSLog(@"%u--wakeUp", port); }); dispatch_activate(source); } - (NSInteger)numberWithHexString:(NSString *)hexString { const char *hexChar = [hexString cStringUsingEncoding:NSUTF8StringEncoding]; int hexNumber; sscanf(hexChar, "%x", &hexNumber); return (NSInteger)hexNumber; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { CFRunLoopWakeUp(CFRunLoopGetCurrent()); } ``` 可以看到每次在点击屏幕时调用 `CFRunLoopWakeUp` 尝试唤醒 RunLoop,然后监听 RunLoop 的 _wakeUpPort,都可以在回调中获取到消息。 7. RunLoop lifecycle ![RunLoop lifecycle](./../assets/2020-08-13-RunLoopLifeCycle.png) 休眠时 RunLoop 被 mach_msg 阻塞,等到消息到来,可以是手动给 RunLoop 的 wakeUpPort 发消息 mach_msg,或者是 timer、Source1。然后继续走到 RunLoop run,不断循环 Timer 不准确的原因:1. RunLoop 底层对 Timer 实现的精度不高,所以需要指定 Timer 的精度;2. RunLoop 在 DoSource0、DoSource1 的时候耗时较多,则导致 Timer 下次执行会不准确