RunLoop是通过内部维护的事件循环来对 事件、消息进行管理的一个对象

事件循环(EventLoop)

  • 没有消息需要处理时,休眠以避免资源占用

    image.png

  • 有消息需要处理时,立刻被唤醒

image.png
用户态我们可以理解为我们日常开发的API接口都是面向我们用户态的。也就是程序员和开发者自行定义和开发的功能。
内核态就是系统或者底层的内核方面的系统调用。

那么为什么main函数不会退出,我们可以看看工程里的main函数

  1. int main(int argc, char * argv[]) {
  2. @autoreleasepool {
  3. return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  4. }
  5. }

其实在main中存在的这个UIApplicationMain这个函数就是在内部启动了一个RunLoop从而不断的接收消息和休眠,从而导致main方法不会退出了。

image.pngimage.png

RunLoop对象

OS中有2套API来访问和使用RunLoop

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
image.png

CFRunLoopRef是开源的 https://opensource.apple.com/tarballs/CF/ ```objectivec //Foundation [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象 [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

//Core Foundation CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象 CFRunLoopGetMain(); // 获得主线程的RunLoop对象

//NSRunLoop <—> CFRunLoopRef 相互转化 NSLog(@”NSRunLoop <—> CFRunloop == %p—%p”,CFRunLoopGetMain() , [NSRunLoop mainRunLoop].getCFRunLoop);

【打印结果】:内存地址相同

0000-00-13 00:30:16.527 MultiThreading[57703:1217113] NSRunLoop <—> CFRunloop == 0x60000016a680—0x60000016a680

  1. <a name="69fa3701"></a>
  2. ## RunLoop的基本作用
  3. 1. 保持程序的持续运行(如:程序一启动就会开启一个主线程(中的 runloop 是自动创建并运行),runloop 保证主线程不会被销毁,也就保证了程序的持续运行)。
  4. 2. 处理App中的各种事件(如:touches 触摸事件、NSTimer 定时器事件、Selector事件(选择器 performSelector))。
  5. 3. 节省CPU资源,提高程序性能(有事情就做事情,没事情就休息 (其资源释放))。
  6. 4. 负责渲染屏幕上的所有UI。
  7. 附:CFRunLoop.c 源码
  8. ```c
  9. #【用DefaultMode启动,具体实现查看 CFRunLoopRunSpecific Line2704】
  10. #【RunLoop的主函数,是一个死循环 dowhile】
  11. void CFRunLoopRun(void) { /* DOES CALLOUT */
  12. int32_t result;
  13. do {
  14. /*
  15. 参数一:CFRunLoopRunSpecific 具体处理runloop的运行情况
  16. 参数二:CFRunLoopGetCurrent() 当前runloop对象
  17. 参数三:kCFRunLoopDefaultMode runloop的运行模式的名称
  18. 参数四:1.0e10 runloop默认的运行时间,即超时为10的九次方
  19. 参数五:returnAfterSourceHandled 回调处理
  20. */
  21. result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
  22. CHECK_FOR_FORK();
  23. //【判断】:如果runloop没有停止 且 没有结束则继续循环,相反侧退出。
  24. } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
  25. }
  26. #【直观表现】
  27. RunLoop 其实内部就是do-while循环,在这个循环内部不断地处理各种任务(`比如Source、Timer、Observer`),
  28. 通过判断result的值实现的。所以 可以看成是一个死循环。
  29. 如果没有RunLoop,UIApplicationMain 函数执行完毕之后将直接返回,就是说程序一启动然后就结束;

Runloop 开启&退出

验证 Runloop 是在UIApplicationMain 中开启。

  1. # int 类型返回值
  2. UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
  3. int main(int argc, char * argv[]) {
  4. @autoreleasepool {
  5. NSLog(@"开始");
  6. int number = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  7. NSLog(@"结束");
  8. return number;
  9. }
  10. }
  11. #【验证结果】:只会打印开始,并不会打印结束。
  12. ----
  13. #【Runloop 的退出条件】。
  14. App退出;线程关闭;设置最大时间到期;

【注解】:说明在UIApplicationMain函数内部开启了一个和主线程相关的RunLoop (保证主线程不会被销毁),导致 UIApplicationMain 不会返回,一直在运行中,也就保证了程序的持续运行。

Runloop和线程关系

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 创建子线程RunLoop,通过[NSRunLoop currentRunLoop]在子线程内部获取,不获取则不会创建,方法调用时,会先查看字典,有则返回,没有则创建并存入字典中。
  • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
  • RunLoop在第一次获取时创建,在线程结束时销毁。

CFRunLoopRef源码

  1. # 1. 主线程相关联的RunLoop创建
  2. // 创建字典
  3. CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
  4. // 创建主线程 根据传入的主线程创建主线程对应的RunLoop
  5. CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
  6. // 保存主线程 将主线程-key和RunLoop-Value保存到字典中
  7. CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
  8. # 2. 创建与子线程相关联的RunLoop
  9. // 从字典中获取子线程的runloop
  10. CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  11. __CFUnlock(&loopsLock);
  12. if (!loop) {
  13. // 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
  14. CFRunLoopRef newLoop = __CFRunLoopCreate(t);
  15. __CFLock(&loopsLock);
  16. loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  17. // 把当前子线程和对应的runloop保存到字典中
  18. if (!loop) {
  19. CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
  20. loop = newLoop;
  21. }
  22. // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  23. __CFUnlock(&loopsLock);
  24. CFRelease(newLoop);
  25. }

RunLoop的运行逻辑

image.png
其内部代码整理如下 :

  1. /// 用DefaultMode启动
  2. void CFRunLoopRun(void) {
  3. CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
  4. }
  5. /// 用指定的Mode启动,允许设置RunLoop超时时间
  6. int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
  7. return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
  8. }
  9. /// RunLoop的实现
  10. int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
  11. /// 首先根据modeName找到对应mode
  12. CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
  13. /// 如果mode里没有source/timer/observer, 直接返回。
  14. if (__CFRunLoopModeIsEmpty(currentMode)) return;
  15. /// 1. 通知 Observers: RunLoop 即将进入 loop。
  16. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
  17. /// 内部函数,进入loop
  18. __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
  19. Boolean sourceHandledThisLoop = NO;
  20. int retVal = 0;
  21. do {
  22. /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
  23. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
  24. /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
  25. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
  26. /// 执行被加入的block
  27. __CFRunLoopDoBlocks(runloop, currentMode);
  28. /// 4. RunLoop 触发 Source0 (非port) 回调。
  29. sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
  30. /// 执行被加入的block
  31. __CFRunLoopDoBlocks(runloop, currentMode);
  32. /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
  33. if (__Source0DidDispatchPortLastTime) {
  34. Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
  35. if (hasMsg) goto handle_msg;
  36. }
  37. /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
  38. if (!sourceHandledThisLoop) {
  39. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
  40. }
  41. /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
  42. /// 一个基于 port 的Source 的事件。
  43. /// 一个 Timer 到时间了
  44. /// RunLoop 自身的超时时间到了
  45. /// 被其他什么调用者手动唤醒
  46. __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
  47. mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
  48. }
  49. /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
  50. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
  51. /// 收到消息,处理消息。
  52. handle_msg:
  53. /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
  54. if (msg_is_timer) {
  55. __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
  56. }
  57. /// 9.2 如果有dispatch到main_queue的block,执行block。
  58. else if (msg_is_dispatch) {
  59. __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
  60. }
  61. /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
  62. else {
  63. CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
  64. sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
  65. if (sourceHandledThisLoop) {
  66. mach_msg(reply, MACH_SEND_MSG, reply);
  67. }
  68. }
  69. /// 执行加入到Loop的block
  70. __CFRunLoopDoBlocks(runloop, currentMode);
  71. if (sourceHandledThisLoop && stopAfterHandle) {
  72. /// 进入loop时参数说处理完事件就返回。
  73. retVal = kCFRunLoopRunHandledSource;
  74. } else if (timeout) {
  75. /// 超出传入参数标记的超时时间了
  76. retVal = kCFRunLoopRunTimedOut;
  77. } else if (__CFRunLoopIsStopped(runloop)) {
  78. /// 被外部调用者强制停止了
  79. retVal = kCFRunLoopRunStopped;
  80. } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
  81. /// source/timer/observer一个都没有了
  82. retVal = kCFRunLoopRunFinished;
  83. }
  84. /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
  85. } while (retVal == 0);
  86. }
  87. /// 10. 通知 Observers: RunLoop 即将退出。
  88. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  89. }

RunLoop休眠的实现原理

从上面代码第7步可以看到,RunLoop 的核心是基于mach port 的,其进入休眠时调用的函数是 mach_msg()

  1. /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
  2. /// 一个基于 port 的Source 的事件。
  3. /// 一个 Timer 到时间了
  4. /// RunLoop 自身的超时时间到了
  5. /// 被其他什么调用者手动唤醒
  6. __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
  7. mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
  8. }

为了实现消息的发送和接收,mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:
image.png image.png

  • 例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方

关于具体 mach port 发送信息可以查看:NSHipster这篇文章,或中文翻译

应用

运行循环与时钟

  1. @interface ViewController ()
  2. @property (nonatomic, strong) NSTimer *timer;
  3. @end
  4. @implementation ViewController
  5. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  6. NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
  7. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  8. }
  9. - (void)fire {
  10. static int num = 0;
  11. /// 耗时操作
  12. for (int i = 0; i < 1000 * 1000; ++i) {
  13. [NSString stringWithFormat:@"hello - %d", i];
  14. }
  15. NSLog(@"%d", num++);
  16. }
  17. @end

运行测试,会发现卡顿非常严重

  • 将时钟添加到其他线程工作
  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  2. self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startTimer) object:nil];
  3. [self.thread start];
  4. }
  5. - (void)startTimer {
  6. @autoreleasepool {
  7. NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fire) userInfo:nil repeats:YES];
  8. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  9. _timerRf = CFRunLoopGetCurrent();
  10. CFRunLoopRun();
  11. NSLog(@"come here");
  12. }
  13. }

注意:主线程的运行循环是默认启动的,但是子线程的运行循环是默认不工作的,这样能够保证线程执行完毕后,自动被销毁

  • 停止运行循环
  1. - (IBAction)stop {
  2. if (_timerRf == NULL) {
  3. return;
  4. }
  5. CFRunLoopStop(_timerRf);
  6. _timerRf = NULL;
  7. }