老生常谈 RunLoop 对象

iOS 中有2套 API 可以访问和使用 RunLoop。分别是

  • Foundation:NSRunLoop

  • CoreFoundation:CFRunLoopRef

//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
  
//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

NSRunLoop 是对 CFRunLoopRef 的一层 OC 包装,所以要了解 RunLoop 的内部结果,就需要了解 CFRunLoopRef

  • 每条线程都有与之一一对应的 RunLoop 对象
  • 主线程的 RunLoop 已经自动创建好了,子线程的 RunLoop 需要主动创建
  • RunLoop 在第一次获取时创建,在线程结束时消失

RunLoop 相关的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef 代表 RunLoop 的运行模式

  • 一个 RunLoop 包含若干个 Mode,每个 Mode 包含若干个 Source/Timer/Observer
  • 每次 RunLoop 启动,只能指定一个 Mode,这个 Mode 被叫做 CurrentMode
  • 如果需要切换 Mode,只能退出 RunLoop,则以一个 Mode 进入
  • 这样做的目的是为了分隔开不同组的 Source/Timer/Observer 互不影响

系统默认注册了5个Mode

  • kCFRunLoopDefaultMode:App 的默认 Mode,通常主线程是在这个 Mode 下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  • UIInitializationRunLoopMode: 在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunLoopSourceRef 事件源(输入源)

  • 早期的分法:
    • Ported-Based Source
    • Custom Input Source
    • Cocoa Perform Selector Source
  • 现在的分法
    • Source0:非基于 port 的,用户主动触发的事件
    • Source1: 基于 port的,通过内核在线程间相互发送消息

CFRunLoopTimerRef 是基于时间的触发器

  • 基本上说就是 NSTimer,它会收到 RunLoopMode 的影响
  • GCD 的 timer 不受 RunLoopMode 的影响

- CFRunLoopObserverRef 观察者,监听 RunLoop 状态的变化

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),                // 即将进入 RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),        // 即将处理 NSTimer
    kCFRunLoopBeforeSources = (1UL << 2),        // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),        // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),        // 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),                // 退出 RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU        
};

添加 Observer

//1、获得当前线程下的 RunLoop
CFRunLoopRef runloop = CFRunLoopGetCurrent();    
//2、为 RunLoop 创建观察者
CFRunLoopObserverRef obersver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {


});
//3、为当前的 RunLoop 添加观察者
CFRunLoopAddObserver(runloop, obersver, kCFRunLoopDefaultMode);
//4、在 CoreFoundation 框架中, create、copy、retain 过的对象都必须在最后 release
CFRelease(obersver);

NSTimer 经常会不准确,原因是什么?

NSTimer 在创建的时候经常会指派到特定的 NSRunLoopMode 中去,举个例子,默认创建的NSTimer 是被添加到 NSRunLoopDefaultMode 中去,当你的页面上有 UIScrollView 或者子类的时如果被拖动了,当前 RunLoop 的 NSRunloopMode 会从 NSDefaultRunLoopMode 转变为 UITrackingRunLoopMode 。遇到这种情况你需要精确的 NSTimer 的话,在创建好 NSTimer 之后,设置 RunLoopMod 为 NSRunLoopCommonModes

NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

NSTimer 会受 NSRunLoopMode 影响,GCD 的 timer 则不会。

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end


@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    /*
     只在默认状态下执行的 NSTimer
    [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"我在执行了");
    }];
     */

    /*
     指定 NSRunLoopMode 的 NSTimer
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
     */

    /*
     GCD 的单位是 纳秒.
     使用 GCD 创建的 timer 正常创建后不会执行,因为创建后设置了指定的时间后触发,所以当代码运行到最后一行的时候,Timer 还没执行,就被销毁了。所以我们必须设置一个属性去保存它。
     */
    //1、创建队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    //2、创建 timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //3、设置 timer 的参数:精准度、时间间隔
    //第三个参数为 GCD timer 的精准度
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //4、为 Timer 设置任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"%@",[NSRunLoop currentRunLoop]);
    });
    //5、执行任务
    dispatch_resume(timer);
}

- (void)show{
    NSLog(@"shw-%@",[NSThread currentThread]);
    NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
@end

监听 RunLoop

//给 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 大哥没事干要睡觉了
*/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-so7emxLG-1585659764330)(https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WX20180801-104553@2x.png)]

上个实验是在主线程对 RunLoop 进行的监听,但是由于是主线程是由系统创建的,所以系统也创建了对应的主 RunLoop,所以我们看不到 RunLoop 创建的状态,为了模拟完整的状态,我们开启子线程,在子线程中模拟

- (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 内部运行原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iynhKKgg-1585659764331)(https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/image-20180801113342611.png)]

  • 图上左上角的 Input source 是早期 RunLoop 的分法,现在分法为:Source0 和 Source1。
    • Source0:非基于 port 的,用户主动触发的事件。
    • Source1:基于 port,通过内核和其它线程互相发送消息
  • RunLoop 我们不能自己手动创建,而是可以通过 [NSRunLoop currentRunLoop] 方法获取,类似于懒加载。系统底层的做法是在全局维护了一个字典,字典的 key 和 value 分别是当前的线程和线程对应的 RunLoop,如果新开辟的线程没有对应的 RunLoop,系统则为其创建 RunLoop,并将其写入字典(线程、为其创建的 RunLoop)

RunLoopMode 的概念

转载于网络

底层实现

内部就是 do-while 的循环,在这个循环内部不断处理各种任务(Timer、Source、Observer)

我们来看看苹果官方开源的 CFRunLoop.c 文件。看几个关键函数的实现猜测下 RunLoop 的内部原理

以下的代码都有注释说明

__CFRunLoopModeIsEmpty

此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer,只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop

// 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

  // 用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 会用到

  // 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 状态。

  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

  /* 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);
                  // <rdar://problem/16393959>
                  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);
              // <rdar://problem/16393959>
              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;
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值