RunLoop
获取 RunLoop
线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。
线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。
RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
你只能在一个线程的内部获取其 RunLoop(主线程除外)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   |  static CFMutableDictionaryRef loopsDic;
  static CFSpinLock_t loopsLock;
 
  CFRunLoopRef _CFRunLoopGet(pthread_t thread) {     OSSpinLockLock(&loopsLock);
      if (!loopsDic) {                  loopsDic = CFDictionaryCreateMutable();         CFRunLoopRef mainLoop = _CFRunLoopCreate();         CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);     }
           CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
      if (!loop) {                  loop = _CFRunLoopCreate();         CFDictionarySetValue(loopsDic, thread, loop);                  _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);     }
      OSSpinLockUnLock(&loopsLock);     return loop; }
  CFRunLoopRef CFRunLoopGetMain() {     return _CFRunLoopGet(pthread_main_thread_np()); }
  CFRunLoopRef CFRunLoopGetCurrent() {     return _CFRunLoopGet(pthread_self()); }
 
  | 
 
结构
runloop 的相关类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

mode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | struct __CFRunLoopMode {     CFStringRef _name;                 CFMutableSetRef _sources0;         CFMutableSetRef _sources1;         CFMutableArrayRef _observers;      CFMutableArrayRef _timers;         ... };
  struct __CFRunLoop {     CFMutableSetRef _commonModes;          CFMutableSetRef _commonModeItems;      CFRunLoopModeRef _currentMode;         CFMutableSetRef _modes;                ... };
  | 
 
一个 Mode,通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中,来标记自己为 common。
每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有 Mode 里。
所以 common 并不是一个真正的 mode。
RunLoop 的内部逻辑

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
   |  void CFRunLoopRun(void) {     CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); }
 
  int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {     return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); }
 
  int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
           CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);          if (__CFRunLoopModeIsEmpty(currentMode)) return;
           __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
           __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
          Boolean sourceHandledThisLoop = NO;         int retVal = 0;         do {
                           __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);                          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);                          __CFRunLoopDoBlocks(runloop, currentMode);
                           sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);                          __CFRunLoopDoBlocks(runloop, currentMode);
                           if (__Source0DidDispatchPortLastTime) {                 Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)                 if (hasMsg) goto handle_msg;             }
                           if (!sourceHandledThisLoop) {                 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);             }
                                                                               __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {                 mach_msg(msg, MACH_RCV_MSG, port);              }
                           __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
                           handle_msg:
                           if (msg_is_timer) {                 __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())             }
                           else if (msg_is_dispatch) {                 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);             }
                           else {                 CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);                 sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);                 if (sourceHandledThisLoop) {                     mach_msg(reply, MACH_SEND_MSG, reply);                 }             }
                           __CFRunLoopDoBlocks(runloop, currentMode);
 
              if (sourceHandledThisLoop && stopAfterHandle) {                                  retVal = kCFRunLoopRunHandledSource;             } else if (timeout) {                                  retVal = kCFRunLoopRunTimedOut;             } else if (__CFRunLoopIsStopped(runloop)) {                                  retVal = kCFRunLoopRunStopped;             } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {                                  retVal = kCFRunLoopRunFinished;             }
                       } while (retVal == 0);     }
           __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }
 
  | 
 
apple 使用 runloop
apple 注册的 runloop mode
app 启动后的 runloop 状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
   | CFRunLoop {     current mode = kCFRunLoopDefaultMode     common modes = {         UITrackingRunLoopMode         kCFRunLoopDefaultMode     }
      common mode items = {
                   CFRunLoopSource {order =-1, {             callout = _UIApplicationHandleEventQueue}}         CFRunLoopSource {order =-1, {             callout = PurpleEventSignalCallback }}         CFRunLoopSource {order = 0, {             callout = FBSSerialQueueRunLoopSourceHandler}}
                   CFRunLoopSource {order = 0,  {port = 17923}}         CFRunLoopSource {order = 0,  {port = 12039}}         CFRunLoopSource {order = 0,  {port = 16647}}         CFRunLoopSource {order =-1, {             callout = PurpleEventCallback}}         CFRunLoopSource {order = 0, {port = 2407,             callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}         CFRunLoopSource {order = 0, {port = 1c03,             callout = __IOHIDEventSystemClientAvailabilityCallback}}         CFRunLoopSource {order = 0, {port = 1b03,             callout = __IOHIDEventSystemClientQueueCallback}}         CFRunLoopSource {order = 1, {port = 1903,             callout = __IOMIGMachPortPortCallback}}
                   CFRunLoopObserver {order = -2147483647, activities = 0x1,              callout = _wrapRunLoopWithAutoreleasePoolHandler}         CFRunLoopObserver {order = 0, activities = 0x20,                       callout = _UIGestureRecognizerUpdateObserver}         CFRunLoopObserver {order = 1999000, activities = 0xa0,                 callout = _afterCACommitHandler}         CFRunLoopObserver {order = 2000000, activities = 0xa0,                 callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}         CFRunLoopObserver {order = 2147483647, activities = 0xa0,              callout = _wrapRunLoopWithAutoreleasePoolHandler}
                   CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,             next fire date = 453098071 (-4421.76019 @ 96223387169499),             callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}     },
      modes = {         CFRunLoopMode  {             sources0 =  {  },             sources1 =  {  },             observers = {  },             timers =    {  },         },
          CFRunLoopMode  {             sources0 =  {  },             sources1 =  {  },             observers = {  },             timers =    {  },         },
          CFRunLoopMode  {             sources0 = {                 CFRunLoopSource {order = 0, {                     callout = FBSSerialQueueRunLoopSourceHandler}}             },             sources1 = (null),             observers = {                 CFRunLoopObserver >{activities = 0xa0, order = 2000000,                     callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}             )},             timers = (null),         },
          CFRunLoopMode  {             sources0 = {                 CFRunLoopSource {order = -1, {                     callout = PurpleEventSignalCallback}}             },             sources1 = {                 CFRunLoopSource {order = -1, {                     callout = PurpleEventCallback}}             },             observers = (null),             timers = (null),         },
          CFRunLoopMode  {             sources0 = (null),             sources1 = (null),             observers = (null),             timers = (null),         }     } }
  | 
 
系统默认注册了 5 个 Mode:
kCFRunLoopDefaultMode: App 的默认 Mode,通常主线程是在这个 Mode 下运行的。
 
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
 
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
 
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
 
kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
 
AutoreleasePool
App 启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入 Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出 Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer 回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的 App 进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
手势识别
当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
苹果注册了一个 Observer 监测 BeforeWaiting (Loop 即将进入休眠) 事件,这个 Observer 的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行 GestureRecognizer 的回调。
当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
界面更新
当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay 方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出 Loop) 事件,回调去执行一个很长的函数:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
定时器
NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop 为了节省资源,并不会在非常准确的时间点回调这个 Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。
如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。
CADisplayLink 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动 TableView 时,即使一帧的卡顿也会让用户有所察觉。
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
GCD
当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop 会被唤醒,并从消息中取得这个 block,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。
但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。
RunLoop 的运用
创建一个长时间存活的线程
我们会经常把一些耗费时间的操作放在子线程处理,不影响主线程 UI 更新。
子线程会在处理完任务后进行销毁
如果需要经常在子线程里执行任务,创建销毁线程会造成资源浪费,使用 RunLoop 来创建一个常驻的子线程会更高效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | 
  + (void)networkRequestThreadEntryPoint:(id)__unused object {     @autoreleasepool {         [[NSThread currentThread] setName:@"AFNetworking"];         NSRunLoop *runLoop = [NSRunLoop currentRunLoop];         [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];         [runLoop run];     } }
  + (NSThread *)networkRequestThread {     static NSThread *_networkRequestThread = nil;     static dispatch_once_t oncePredicate;     dispatch_once(&oncePredicate, ^{         _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];         [_networkRequestThread start];     });     return _networkRequestThread; }
 
 
  | 
 
界面滑动时保证 NSTimer 正常运行
1 2 3 4 5 6 7
   |  NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(foo) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; [timer fire];
 
 
 
 
  | 
 
保证界面流畅
AsyncDisplayKit
ASDK 仿照 QuartzCore/UIKit 框架的模式,实现了一套类似的界面更新的机制:
在主线程的 RunLoop 中添加一个 Observer,监听了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。
RunLoop 监测主线程卡顿
在主线程的 RunLoop 中添加一个 observer,检测 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 的时间并记录。
连续 3 次超时 80ms 认为卡顿
戴铭-GCDFetchFeed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
   | @interface SMLagMonitor() {     int timeoutCount;     CFRunLoopObserverRef runLoopObserver;     @public     dispatch_semaphore_t dispatchSemaphore;     CFRunLoopActivity runLoopActivity; } @property (nonatomic, strong) NSTimer *cpuMonitorTimer; @end
  @implementation SMLagMonitor
  #pragma mark - Interface + (instancetype)shareInstance {     static id instance = nil;     static dispatch_once_t dispatchOnce;     dispatch_once(&dispatchOnce, ^{         instance = [[self alloc] init];     });     return instance; }
  - (void)beginMonitor {     self.isMonitoring = YES;          self.cpuMonitorTimer = [NSTimer scheduledTimerWithTimeInterval:3                                                              target:self                                                            selector:@selector(updateCPUInfo)                                                            userInfo:nil                                                             repeats:YES];          if (runLoopObserver) {         return;     }     dispatchSemaphore = dispatch_semaphore_create(0);           CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};     runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,                                               kCFRunLoopAllActivities,                                               YES,                                               0,                                               &runLoopObserverCallBack,                                               &context);          CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
           dispatch_async(dispatch_get_global_queue(0, 0), ^{                  while (YES) {             long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, STUCKMONITORRATE * NSEC_PER_MSEC));             if (semaphoreWait != 0) {                 if (!runLoopObserver) {                     timeoutCount = 0;                     dispatchSemaphore = 0;                     runLoopActivity = 0;                     return;                 }                                  if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {                                          if (++timeoutCount < 3) {                         continue;                     }
                      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{                         NSString *stackStr = [SMCallStack callStackWithType:SMCallStackTypeMain];                         SMCallStackModel *model = [[SMCallStackModel alloc] init];                         model.stackStr = stackStr;                         model.isStuck = YES;                         [[[SMLagDB shareInstance] increaseWithStackModel:model] subscribeNext:^(id x) {}];                     });                 }              }             timeoutCount = 0;         }     });
  }
  - (void)endMonitor {     self.isMonitoring = NO;     [self.cpuMonitorTimer invalidate];     if (!runLoopObserver) {         return;     }     CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);     CFRelease(runLoopObserver);     runLoopObserver = NULL; }
  #pragma mark - Private - (void)updateCPUInfo {     [SMCPUMonitor updateCPU]; }
  static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){     SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;     lagMonitor->runLoopActivity = activity;
      dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;     dispatch_semaphore_signal(semaphore); }
  | 
 
参考
深入理解 RunLoop by:ibireme