RunLoop对象

  • CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
  • NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
  • CFRunLoop结构如下
    1. struct __CFRunLoop {
    2. pthread_t _pthread;
    3. CFMutableSetRef _commonModes; // Set
    4. CFMutableSetRef _commonModeItems; // Set
    5. CFRunLoopModeRef _currentMode; // Current Runloop Mode
    6. CFMutableSetRef _modes; // Set
    7. ...
    8. };

    CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF 下载到整个 CoreFoundation 的源码。

内部关系

  • CFRunLoop
  • CFRunLoopMode
  • Source / Timer / Observer

内部关系如图:
image.png

  1. CFRunLoopMode代表的是RunLoop的运行模式。
  2. 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer
  3. 每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
  4. 如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

注意:Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

Mode

RunLoop 有五种运行模式,其中常见的有1.2两种

  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes: 这是一个占位用的Mode,不是实际存在的一种Mode,是同步 Source/Timer/Observer到多个 Mode 中的一种技术方案

CFRunLoopMode 的结构大致如下:

  1. struct __CFRunLoopMode {
  2. CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
  3. CFMutableSetRef _sources0; // Set
  4. CFMutableSetRef _sources1; // Set
  5. CFMutableArrayRef _observers; // Array
  6. CFMutableArrayRef _timers; // Array
  7. ...
  8. };

CFRunLoop对外暴露的管理 Mode 接口只有下面2个:

  1. CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
  2. CFRunLoopRunInMode(CFStringRef modeName, ...);

Mode 暴露的管理 mode item 的接口有下面几个:

  1. CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
  2. CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
  3. CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
  4. CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
  5. CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
  6. CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
  • _commonModes:一个 mode 可以标记为 Common 属性,主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultModeUITrackingRunLoopMode都已经被标记为Common属性,当然你也可以通过调用 CFRunLoopAddCommonMode() 方法将自定义mode 放到 kCFRunLoopCommonModes 组合。
  • commonModeItems:存放的source, observer, timer等,在每次 runLoop 运行的时候都会被同步到具有 Common 标记的 Modes 里。如:[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes] 就是把timer放到commonModeItems 里。
  • 更多系统或框架 Mode查看这里

Source

CFRunLoopSource 是事件产生的地方。

  • Source0 (负责App内部事件,由App负责管理触发,例如UITouch事件) 只包含了一个回调(函数指针),它不能主动触发事件。使用时,你需要先调用CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,需要手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,就会处理并调用事件处理方法。
  • Source1 包含了一个 mach_port 和一个回调(函数指针),可以监听系统端口和其他线程相互发送消息,能主动唤醒 RunLoop(由操作系统内核进行管理,例如CFMessagePort消息)。

image.png

  • 【Port-Based Sources】:基于端口的源 (对应的是source1):与内核端口相关,只需要简单的创建端口对象,并使用 NSPort 的方法将端口对象加入到runloop,端口对象会处理创建以及配置输入源对应,Source1和Timer都属于端口事件源,不同的是所有的Timer都共用一个端口Mode Timer Port,而每个Source1都有不同的对应端口
  • 【Custom Input Sources】:自定义源:使用CFRunLoopSourceRef 类型相关的函数 (线程) 来创建自定义输入源。
  • 【Perform Selector Sources】:performSelector:OnThread:delay:

Timer

CFRunLoopTimer 是基于时间的触发器,上层对应NSTimer
,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

Observer

  1. struct __CFRunLoopObserver {
  2. CFRuntimeBase _base;
  3. pthread_mutex_t _lock;
  4. CFRunLoopRef _runLoop;
  5. CFIndex _rlCount;
  6. CFOptionFlags _activities; /* immutable */
  7. CFIndex _order; /* immutable */
  8. CFRunLoopObserverCallBack _callout; /* immutable */
  9. CFRunLoopObserverContext _context; /* immutable, except invalidation */
  10. }

CFRunLoopObserver 相当于消息循环中的一个监听器,随时通知外部当前RunLoop的运行状态(它包含一个函数指针_callout将当前状态及时告诉观察者)。具体的Observer状态如下

  1. -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  2. {
  3. //创建监听者
  4. /*
  5. 第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
  6. 第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
  7. 第三个参数 Boolean repeats:YES:持续监听 NO:不持续
  8. 第四个参数 CFIndex order:优先级,一般填0即可
  9. 第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
  10. */
  11. /*
  12. 所有事件
  13. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity{
  14. kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
  15. kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
  16. kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
  17. kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
  18. kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
  19. kCFRunLoopExit = (1UL << 7), // 即将退出Loop
  20. };
  21. */
  22. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  23. switch (activity) {
  24. case kCFRunLoopEntry:
  25. NSLog(@"RunLoop进入");
  26. break;
  27. case kCFRunLoopBeforeTimers:
  28. NSLog(@"RunLoop要处理Timers了");
  29. break;
  30. case kCFRunLoopBeforeSources:
  31. NSLog(@"RunLoop要处理Sources了");
  32. break;
  33. case kCFRunLoopBeforeWaiting:
  34. NSLog(@"RunLoop要休息了");
  35. break;
  36. case kCFRunLoopAfterWaiting:
  37. NSLog(@"RunLoop醒来了");
  38. break;
  39. case kCFRunLoopExit:
  40. NSLog(@"RunLoop退出了");
  41. break;
  42. default:
  43. break;
  44. }
  45. });
  46. // 给RunLoop添加监听者
  47. /*
  48. 第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
  49. 第二个参数 CFRunLoopObserverRef observer 监听者
  50. 第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
  51. */
  52. CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  53. /*
  54. CF的内存管理(Core Foundation)
  55. 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
  56. GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
  57. */
  58. CFRelease(observer);
  59. }

Call out

在开发过程中几乎所有的操作都是通过Call out进行回调的(无论是Observer的状态通知还是Timer、Source的处理),而系统在回调时通常使用如下几个函数进行回调(换句话说你的代码其实最终都是通过下面几个函数来负责调用的,即使你自己监听Observer也会先调用下面的函数然后间接通知你,所以在调用堆栈中经常看到这些函数):

  1. {
  2. /// 1. 通知Observers,即将进入RunLoop
  3. /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
  4. __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
  5. do {
  6. /// 2. 通知 Observers: 即将触发 Timer 回调。
  7. __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
  8. /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
  9. __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
  10. __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
  11. /// 4. 触发 Source0 (非基于port的) 回调。
  12. __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
  13. __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
  14. /// 6. 通知Observers,即将进入休眠
  15. /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
  16. __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
  17. /// 7. sleep to wait msg.
  18. mach_msg() -> mach_msg_trap();
  19. /// 8. 通知Observers,线程被唤醒
  20. __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
  21. /// 9. 如果是被Timer唤醒的,回调Timer
  22. __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
  23. /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
  24. __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
  25. /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
  26. __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
  27. } while (...);
  28. /// 10. 通知Observers,即将退出RunLoop
  29. /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
  30. __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
  31. }

例如在控制器的touchBegin中打入断点查看堆栈(由于UIEvent是Source0,所以可以看到一个Source0的Call out函数CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION调用):
image.png