iOS RunLoop 详解

转自 iOS RunLoop 详解

image.png

Runloop 是和线程紧密相关的基础组件,是很多多线程有关功能的幕后功臣。尽管在平常使用中几乎不会太直接用到,理解 Runloop 有利于我们更加深入的理解 iOS 的多线程模型。

本文从如下几个方面理解RunLoop的相关知识点。

  • RunLoop 概念
  • RunLoop 实现
  • RunLoop 运用
  • RunLoop 应用

RunLoop 概念

RunLoop 介绍

RunLoop 是什么?RunLoop 还是比较顾名思义的一个东西,说白了就是一种循环,只不过它这种循环比较高级。一般的 while 循环会导致 CPU 进入忙状态,而 RunLoop 则是一种“闲”等待,这部分可以类比 Linux 下的 epoll。当没有事件时,RunLoop 会进入休眠状态,有事件发生时,RunLoop 会去找相应的 Hander 处理事件。RunLoop 可以让线程需要做事的时候忙起来,不需要的话就让线程休眠。

从代码上看,RunLoop 其实就是一个对象,它的结构如下,源码看这里

struct __CFRunLoop {
  CFRuntimeBase _base;
  pthread_mutex_t _lock; /* locked for accessing mode list */
  __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒 RunLoop
  Boolean _unused;
  volatile _per_run_data *_perRunData; // reset for runs of the run loop
  pthread_t _pthread; // RunLoop 对应的线程
  uint32_t _winthread;
  CFMutableSetRef _commonModes; // 存储的是字符串,记录所有标记为 common 的 mode
  CGMutableSetRef _commonModeItems; // 存储所有 commonMode 的 item(source、timer、observer)
  CFRunLoopModeRef _currentMode; // 当前运行的 mode
  CFMutableSetRef _modes; // 存储的是 CFRunLoopModeRef
  struct _block_item *_blocks_head; // doblocks 的时候用到
  struct _block_item *_blocks_tail;
  CGTypeRef _counterpart;
}

可见,一个 RunLoop 对象,主要包含了一个线程,若干个 Mode,若干个 commonMode,还有一个当前运行的 Mode。

RunLoop 与线程

当我们需要一个常驻线程,可以让线程需要做事的时候忙起来,不需要的话就让线程休眠。我们就在线程里面执行下面这个代码,一直等待消息,线程就不会退出了。

do {
  // 获取消息
  // 处理消息
} while (消息 != 退出)

上面的这种循环模型被称作 Event Loop,事件循环模型在众多系统里都有实现,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于函数内部“接收消息->等待->处理”的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

下图描述了Runloop运行流程(基本描述了上面Runloop的核心流程,当然可以查看官方The Run Loop Sequence of Events描述):

runLoopFCD.jpg

整个流程并不复杂(需要注意的就是黄色区域的消息处理中并不包含source0,因为它在循环开始之初就会处理),整个流程其实就是一种Event Loop的实现,其他平台均有类似的实现,只是这里叫做RunLoop。

RunLoop与线程的关系如下图

image.png

图中展现了 RunLoop 在线程中的作用:从 input source 和 timer source 接受事件,然后在线程中处理事件。

RunLoop 和线程是绑定在一起的。每个线程(包括主线程)都有一个对应的 RunLoop 对象。我们并不能自己创建 RunLoop 对象,但是可以获取到系统提供的 RunLoop 对象。

主线程的 RunLoop 会在应用启动的时候完成启动,其他线程的 RunLoop 默认并不会启动,需要我们手动启动。

RunLoop Mode

Mode 可以视为事件的管家,一个 Mode 管理着各种事件,它的结构如下:

struct __CFRunLoopMode {
  CFRuntimeBase _base;
  pthread_mutex_t _lock; /* must have the run loop locked before locking this */
  CFStringRef _name; // mode 名称
  Boolean _stopped; // mode 是否被终止
  char _padding[3];
  // 几种事件
  CFMutableSetRef _sources0; // source0
  CFMutableSetRef _sources1: // sources1
  CFMutableArrayRef _observers; // 通知
  CFMutableArrayRef _times; // 定时器
  CFMutableDictionaryRef _portToV1SourceMap; // 字典 key 是 mach_port_t,value 是 CFRunLoopSourceRef
  __CFPortSet _portSet; // 保存所有需要监听的 port,比如 _weakUpPort, _timerPort 都保存在这个数据中
  CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
  dispatch_source_t _timerSource;
  dispatch_queue_t _queue;
  Boolean _timerFired; // set to true by the source when a timer has fired
  Boolean _dispatchTimerArmed;
#endif
#if DEPLOYMENAT_TARGET_WINDOWS
  DWORD _msgQMask;
  void (* _msgPump)(void);
#endif
  uint64_t _timerSoftDeadline;
  uint64_t _timerHardDeadline;
}

一个 CFRunLoopMode 对象有一个 name,若干 source0、 source1、timer、observer 和若干 port,可见事件都是由 Mode 在管理, 而 RunLoop 管理 Mode。

从源码很容易看出,RunLoop 总是运行在某种特定的 CFRunLoopModeRef 下(每次运行 __CFRunLoopRun() 函数时必须指定 Mode)。而通过 CFRunLoopRef 对应结构体的定义可以很容易知道每种 RunLoop 都可以包含个 Mode,每个 Mode 有包含 Source/Timer/Observer。每次调用 RunLoop 的主函数 __CFRunLoopRun() 时必须指定一种 Mode,这个 Mode 称为 _curentMode,当切换 Mode 时必须退出当前 Mode,然后重新进入 RunLoop 以保证不同 Mode 的 Source/Timer/Observer 互不影响。

image.png

如图所示,RunLoop Mode 实际上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的 Source,Timer 和 Observer 隔绝开来。RunLoop 在某个时刻只能跑在一个 Mode 下,处理这一个 Mode 当中的 Source,Timer 和 Observer。

苹果文档中提到的 Mode 有五个,分别是:

  • NSDefaultRunLoopMode
  • NSConnectionReplyMode
  • NSModalPanelRunLoopMode
  • NSEventTrackingRunLoopMode
  • NSRunLoopCommonModes

iOS 中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode(注意:并不是说 RunLoop 会运行在 kCFRunLoopCommonModes 这种模式下,而是相当于分别注册了 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。当然你也可以通过调用 CFRunLoopAddCommonMode() 方法将自定义 Mode 放到 kCFRunLoopCommonModes 组合)。

五种 Mode 的介绍如下:

mode name description
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) 用的最多的模式,大多数情况下应该使用该模式开始 RunLoop 并配置 input source
Connection NSConnectionReplyMode (Cocoa) Cocoa 用这个模式结合 NSConnection 对象监测回应,我们应该很少使用这种模式
Modal NSModalPanelRunLoopMode (Cocoa) Cocoa 用此模式来表示用于模态面板的事件
Event tracking NSEventTrackingRunLoopMode (Cocoa) Cocoa 使用此模式在鼠标拖动 loop 和其它用户界面跟踪 loop 期间限制传入事件
Common modes NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) 这是一种可配置的常用模式。将输入源与这些模式相关联会与组中的每个模式相关联。Cocoa applications 里面包括 Default、Modal 和 Event tracking。Core Foundation 只包含默认模式,你可以自己把自定义 mode 用 CFRunLoopAddCommonMode 函数加入到集合中。

RunLoop Source

RunLoop source 分为 Source、Observer、Timer 三种,它们统称为 ModelItem。

CFRunLoopSource

根据官方的描述,CFRunLoopSource 是对 input sources 的抽象。CFRunLoopSource 分为 source0 和 source1 两个版本,它的结构如下:

struct __CFRunLoopSource {
  CFRuntimeBase _base;
  uint32_t _bits; // 用于标记 Signaled 状态,source0 只有在被标记为 Signaled 状态,才会被处理
  pthread_mutex_t _lock;
  CFIndex _order; /* immutable */
  CFMutableBagRef _runLoops;
  union {
    CFRunLoopSourceContext version0; /* immutable, except invalidation */
    CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
  } _context;
};

source0 是 App 内部事件,由 App 自己管理的 UIEvent、CFSocket 都是 source0.当一个 source0 事件准备执行的时候,必须要先把它标记为 signal 状态,以下是 source0 的结构体:

typedef struct {
  CFIndex version;
  void *info;
  const void *(*retain)(const void *info);
  void (*release)(const void *info);
  CFStringRef (*copyDescription)(const void *info);
  Boolean (*equal)(const void *info1, const void *info2);
  CFHashCode (*hash)(const void *info);
  void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
  void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
  void (*perform)(void *info);
} CFRunLoopSourceContext;

source0 是非基于 Port 的。只包含了一个回调(函数指针),它并不能主动触发事件。石永师,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

source1 由 RunLoop 和内核管理,source1 带有 mach_port_t,可以接收内核消息并触发回调,以下是 source1 的结构体:

typedef struct {
  CFIndex version
  void *info;
  const void *(*retain)(const void *info);
  void (*release)(const void *info);
  CFStringRef (*copyDescription)(const void *info);
  Boolean (*equal)(const void *info1, const void *info2);
  CFHashCode (*hash)(const void *info);
#if ((TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
  mach_port_t (*getPort)(void *info);
  void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
  void * (*getPort)(void *info);
  void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

Source1 除了包含回调指针外包含一个 mach port,Source1 可以监听系统端口和通过内核和其他线程通信,接收、分发系统事件,它能够主动唤醒 RunLoop(由操作系统内核进行管理,例如 CFMessagePort 消息)。官方也指出可以自定义 Source,因此对于 CFRunLoopSourceRef 来说它更像一种协议,框架已经默认定义了两种实现,如果有必要开发人员也可以自定义,详细情况可以查看官方文档

CFRunLoopObserver

CFRunLoopObserver 是观察者,可以观察 RunLoop 的各种状态,并抛出回调

struct __CFRunLoopObserver {
  CFRuntimeBase _base;
  pthread_mutex_t _lock;
  CFRunLoopARef _runLoop;
  CFIndex _rlCount;
  CFOptionFlags _activities; /* immutable */
  CFIndex _order; /* immutable */
  CFRunLoopObserverCallBack _callout; /* immutable */
  CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

CFRunLoopObserver 可以观察的状态有如下6种:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
  kCFRunLoopEntry = (1UL << 0), // 即将进入 run loop
  kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 timer
  kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 source
  kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
  kCFRunLoopAfterWaiting = (1UL << 6), // 被唤醒但是还没开始处理事件
  kCFRunLoopExit = (1UL << 7), // run loop 已经退出
  kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoop 通过监控 Source 来决定有没有任务要做,除此之外,我们还可以用 RunLoop Observer 来监控 RunLoop 本身的状态。RunLoop Observer 可以监控上面的 RunLoop 事件,具体流程如下图。

runLoopFCD2.jpg

CFRunLoopTimer

CFRunLoopTimer 是定时器,可以在设定的时间点抛出回调,它的结构如下:

struct __CFRunLoopTimer {
  CFRuntimeBase _base;
  uint16_t _bits; // 标记fire状态
  pthread_mutex_t _block;
  CFRunLoopRef _runLoop; // 添加该 timer 的 runloop
  CFMutableSetRef _rlModes; // 存放所有包含该 timer 的 modeName,意味着一个 timer 可能会在多个 mode 中存在
  CFAbsoluteTime _nextFireDate;
  CFTimeInterval _interval; // 理想时间间隔 /* immutable */
  CFTimeInterval _tolerance; // 时间偏差 /* mutable */
  uint64_t _fireTSR; /* TRS units */
  CFIndex _order; /* immutable */
  CFRunLoopTimerCallBack _callout; /* immutable */
  CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

另外根据官方文档的描述,CFRunLoopTimer和NSTimer是toll-free bridged的,可以相互转换。

CFRunLoopTimer is “toll-free bridged” with its Cocoa Foundation counterpart, NSTimer. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object.

所有 CFRunLoopTimer 具有以下特征:

  • CFRunLoopTimer 是定时器,可以在设定的时间点抛出回调
  • CFRunLoopTimer 和 NSTimer 是 toll-free bridged 的,可以互相转换

RunLoop 实现

下面从以下3个方面介绍RunLoop的实现。

  • 获取 RunLoop
  • 添加 Mode
  • 添加 RunLoop Source

获取 RunLoop

从苹果开放的 API 来看,不允许我们直接创建 RunLoop 对象,只能通过以下几个函数来获取 RunLoop:

  • CFRunLoopRef CFRunLoopGetCurrent(void)
  • CFRunLoopRef CFRunLoopGetMain(void)
  • +(NSRunLoop *)currentRunLoop
  • +(NSRunLoop *)mainRunLoop

前两者是 Core Foundation 中的 API,后两者是 Foundation 中的 API。

那么RunLoop是什么时候被创建的呢?

我们从下面几个函数内部看看。

CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) {
  CHECK_FOR_FORK();
  CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
  if (rl) return rl;
  // 传入当前线程
  return _CFRunLoopGet0(pthread_self());
}

在 CFRunLoopGetCurrent 函数中调用了 _CFRunLoopGet0(),传入的参数是当前线程 pthread_self()。这里可以看出,CFRunLoopGetCurrent 函数必须要在线程内部调用,获取当前线程的 RunLoop。也就是说子线程的 RunLoop 必须要在子线程内部获取。

CFRunLoopGetMain
// 取主线程的 RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
  CHECK_FOR_FORK();
  static CFRunLoopRef __main = NULL; no retain needed
  // 传入主线程
 if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
  return __main;
}

在 CFRunLoopGetMain 函数内部也调用了 _CFRunLoopGet0(),传入的参数是主线程 pthread_main_thread_np()。可以看出,CFRunLoopGetMain() 不管在主线程还是子线程中调用,都可以获取到主线程的 RunLoop。

CFRunLoopGet0

前面两个函数都使用了 CFRunLoopGet0 实现传入线程的函数,下面看下 CFRunLoopGet0 的结构是咋样的。

static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// t==0 is a synonym for "main thread" that always works
// 根据线程取 RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
  }
  __CFSpinLock(&loopsLock);
  // 如果存储 RunLoop 的字典不存在
  if (!__CFRunLoops) {
    __CFSpinUnlock(&loopsLock);
    // 创建一个临时字典 dict
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    // 创建主线程的 RunLoop
    CFRunLoopRef mainLoop = _CFRunLoopCreate(pthread_main_thread_np());
    // 把主线程的 RunLoop 保存到 dict 中,key 是线程,value 是 RunLoop
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    // 此处 NULL 和 __CFRunLoops 指针都指向 NULL,匹配,所有将 dict 写到 __CFRunLoops
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&_CFRunLoops)) {
      // 释放 dict
      CFRelease(dict);
    }
    // 释放 mainrunloop
    CFRelease(mainLoop);
    __CFSpinLock(&loopsLock);
  }
  //以上说明,第一次进来的时候,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建
  //从字典__CFRunLoops中获取传入线程t的runloop
  CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  __CFSpinUnlock(&loopsLock);
  // 如果没有获取到
  if (!loop) {
    // 根据线程 t 创建一个 runloop
    CFRunLoopRef newLoop = __CFRunLoopCreat(t);
    __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
      // 把 newLoop 存入字典 __CFRunLoops,key 是线程 t
      CFDictionarySetValue(__CFRunLoops, pthreadPoint(t), newLoop);
      loop = newLoop;
    }
    // don't release run loops inside the loopsLock, because CFRunLoopDellocate may end up taking it
    __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
  }
  // 如果传入线程就是当前线程
  if (pthread_equal(t, pthread_self())) {
    _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
    if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
      // 注册一个回调,当线程销毁时,销毁对应的 RunLoop
      _CFSetTSD(__CFTSDkeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
    }
  }
  return loop;
}

这段代码可以总结得出以下结论:

  • RunLoop 和线程是一一对应的,对应的方式是以 key-value 的方式保存在一个全局字典中
  • 主线程的 RunLoop 会在初始化全局字典时创建
  • 子线程的 RunLoop 会在第一次获取的时候创建,如果不获取的话就一直不会被创建
  • RunLoop 会在线程销毁时销毁

添加 Mode

在 Core Foundation 中,针对 Mode 的操作,苹果只开放了以下三个 API(Cocoa 中也有功能一样的函数,不再列出):

  • CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
  • CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
  • CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl)

CFRunLoopAddCommonMode Adds a mode to the set of run loop common modes. 向当前RunLoop的common modes中添加一个mode。

CFRunLoopCopyCurrentMode Returns the name of the mode in which a given run loop is currently running. 返回当前运行的mode的name

CFRunLoopCopyAllModes Returns an array that contains all the defined modes for a CFRunLoop object. 返回当前RunLoop的所有mode

我们没有办法直接创建一个 CFRunLoopMode 对象,但是我们可以调用 CFRunLoopAddCommonMode 传入一个字符串向 RunLoop 中添加 Mode,传入的字符串即为 Mode 的名字,Mode 对象应该是此时在 RunLoop 内部创建的。下面来看一下源码。

CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
  CHECK_FOR_FORK();
  if (__CFRunLoopIsDeallocating(rl)) return;
  __CFRunLoopLock(rl);
  // 看 rl 中是否已经有这个 mode,如果有就什么也不做
  if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    // 把 modeName 添加到 RunLoop 的 _commonModes 中
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
      CFTypeRef context[2] = {rl, modeName};
      /* add all common-modes items to new mode */
      // 这里调用 CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer 的时候会调用
      // __CFRunLoopFindMode(rl, modeName, true), CFRunLoopMode 对象在这个时候被创建
      CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
      CFRelease(set);
    }
  } else {
  }
  __CFRunLoopUnlock(rl);
}

可以看得出:

  • modeName 不能重复,modeName 是 mode 的唯一标识符
  • RunLoop 的 _commonModes 数组存放所有被标记为 common 的 mode 的名称
  • 添加 commonMode 会把commonModelItems 数组中的所有 source 同步到新添加的 mode 中
  • CFRunLoopMode 对象在 CFRunLoopAddItemsToCommonMode 函数中调用 CFRunLoopFindMode 时被创建
CFRunLoopCopyCurrentMode/CFRunLoopCopyAllModes

CFRunLoopCopyCurrentMode 和 CFRunLoopCopyAllModes 的内部逻辑比较简单,直接取 RunLoop 的 _currentMode 和 _modes 返回,就不贴源码了。

添加 RunLoop Source (ModeItem)

我们可以通过以下接口添加/移除各种事件:

  • void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
  • void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
  • void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
  • void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
  • void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
  • void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
CFRunLoopAddSource

CFRunLoopAddSource 的代码结构如下:

//添加source事件
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
  CHECK_FOR_FORK();
  if (__CFRunLoopIsDeallocating(rl)) return;
  if (!__CFIsValid(rls)) return;
  Boolean doVer0Callout = false;
  __CFRunLoopLock(rl);
  // 如果是 kCFRunLoopCommonModes
  if (modeName == kCFRunLoopCommonModes) {
    // 如果 runloop 的 _commonModes 存在,则 copy 一个新的复制给 set
    CFSetRef set = rl->commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    // 如果 runloop 的 _commonModeItems 为空
    if (NULL == rl->_commonModeItems) {
      // 先初始化
      rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
    // 把传入的 CFRunLoopSourceRef 加入 _commonModeItems
    CFSetAddValue(rl->_commonModeItems, rls);
    // 如果刚才 set copy 到的数组里面有数据
    if (NULL != set) {
      CFTypeRef context[2] = {rl, rls};
      /* add new item to all common-modes */
      // 则把set里的所有 mode 都执行一遍 __CFRunLoopAddItemToCommonModes 函数
      CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
      CFRelease(set);
    }
    // 以上分支的逻辑就是,如果你往 kCFRunLoopCommonModes 里面添加一个 source,那么所有 _commonModes 里面的 mode 都会添加这个 source
  } else {
    // 根据 modeName 查找 mode
    CFRunLoopModeRef rlm = __CFRunLoopFIndMode(rl, modeName, true);
    // 如果 _sources0 不存在,则初始化 _sources0 和 _portToV1SourceMap
    if (NULL != rlm && NULL == rlm->_sources0) {
      rlm->sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
      rlm->sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBadks);
      rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
    }
    // 如果 _sources0 和 _source1 中都不包含传入的 source
    if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
      // 如果 version 是 0,则加到 _sources0
      if (0 == rls->_context.version0.version) {
        CFSetAddValue(rlm->_sources0, rls);
      // 如果 version 是 1,则加到 _sources1
      } else if (1 == rls->_context.version0.version) {
        CFSetAddValue(rlm->_sources1, rls);
        __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
        if (CFPORT_NULL != src_port) {
          // 此处只有在加到 source1 的时候才会把 souce 和 一个 mach_port_t 对应起来
          // 可以理解为,source1 可以通过内核向其端口发送消息来主动唤醒 runloop
          CFDictionarySetValue(rlm->_portAToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
          __CFPortSetInsert(scr_port, rlm->_portSet);
        }
      }
      __CFRunLoopSourceLock(rls);
      // 把 runloop 加入到 source 的 _runLoops 中
      if (NULL == rls->_runLoops) {
        rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks);
      }
      CFBagAddValue(rls->_runLoops, rl);
      __CFRunLoopSourceUnlock(rls);
      if (0 == rls->_context.version0.version) {
        if (NULL != rls->_context.version0.schedule) {
          doVer0Callout = true;
        }
      }
    }
    if (NULL != rlm) {
      __CFRunLoopModeUnlock(rlm);
    }
  }
  __CFRunLoopUnlock(rl);
  if (doVer0Callout) {
    // although it looses some protection for the source, we have no choice but
    // to do this after unlocking the run loop and mode locks, to avoid deadlocks
    // where the source wants to take a lock which is already held in another
    // thread which is itself waiting for a run loop/mode lock
    rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
  }
}

通过添加 source 的这段代码可以得出如下结论:

  • 如果 modeName 传入 kCFRunLoopCommonModes, 则该 source 会被保存到 RunLoop 的 _commonModeItems 中
  • 如果 modeName 传入 kCFRunLoopCommonModes, 则该 source 会被添加到所有 commonMode 中
  • 如果 modeName 传入的不是 kCFRunLoopCommonModes,则会先查找该 Mode,如果没有,会创建一个
  • 同一个 source 在一个 mode 中只能被添加一次
CFRunLoopRemoveSource

remove 操作和 add 操作的逻辑基本一致,很容易理解。

//移除source
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
    CHECK_FOR_FORK();
    Boolean doVer0Callout = false, doRLSRelease = false;
    __CFRunLoopLock(rl);
    //如果是kCFRunLoopCommonModes,则从_commonModes的所有mode中移除该source
    if (modeName == kCFRunLoopCommonModes) {
        if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
            CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
            CFSetRemoveValue(rl->_commonModeItems, rls);
            if (NULL != set) {
                CFTypeRef context[2] = {rl, rls};
                /* remove new item from all common-modes */
                CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
                CFRelease(set);
            }
        } else {
        }
    } else {
        //根据modeName查找mode,如果不存在,返回NULL
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
        if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
            CFRetain(rls);
            //根据source版本做对应的remove操作
            if (1 == rls->_context.version0.version) {
                __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
                if (CFPORT_NULL != src_port) {
                    CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
                    __CFPortSetRemove(src_port, rlm->_portSet);
                }
            }
            CFSetRemoveValue(rlm->_sources0, rls);
            CFSetRemoveValue(rlm->_sources1, rls);
            __CFRunLoopSourceLock(rls);
            if (NULL != rls->_runLoops) {
                CFBagRemoveValue(rls->_runLoops, rl);
            }
            __CFRunLoopSourceUnlock(rls);
            if (0 == rls->_context.version0.version) {
                if (NULL != rls->_context.version0.cancel) {
                    doVer0Callout = true;
                }
            }
            doRLSRelease = true;
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
    if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but
        // to do this after unlocking the run loop and mode locks, to avoid deadlocks
        // where the source wants to take a lock which is already held in another
        // thread which is itself waiting for a run loop/mode lock
        rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName);   /* CALLOUT */
    }
    if (doRLSRelease) CFRelease(rls);
}
添加 Observer 和 Timer

添加 observer 和 timer 的内部逻辑和添加 source 大体类似。

区别在于 observer 和 timer 只能被添加到一个 RunLoop 的一个或者多个 mode 中,比如一个 timer 被添加到主线程的 RunLoop 中,则不能再把该 timer 添加到子线程的 RunLoop,而 source 没有这个限制,不管是哪个 RunLoop,只要 mode 中没有,就可以添加。

这个区别在文章最开始的结构体中也可以发现,CFRunLoopSource 结构体中有保存 RunLoop 对象的数组,而 CFRunLoopObserver 和 CFRunLoopTimer 只有单个 RunLoop 对象。

RunLoop 运行

在 Core Foundation 中我们可以通过以下 2 个 API 来让 RunLoop 运行:

  • void CFRunLoopRun(void)
    在默认的 mode 下运行当前线程的 RunLoop

  • CFRunLoopRunResult CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
    在指定 mode 下运行当前线程的 RunLoop

CFRunLoopRun

// 默认运行 runloop 的 kCFRunLoopDefaultMode
void CFRunLoopRun(void) { /* DOES CALLOUT */
  int32_t result;
  do {
    // 默认在 KCFRunLoopDefaultMode 下运行 runloop
    result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    CHECK_FOR_FORK();
  } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

在 CFRunLoopRun 函数中调用了 CFRunLoopRunSpecific 函数,runloop 参数传入当前 RunLoop 对象,modeName 参数传入 KCFRunLoopDefaultMode。验证了前面文档的解释。

CFRunLoopRunInMode

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
  CHECK_FOR_FORK();
  return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

在 CFRunLoopRunInMode 函数中也调用了 CFRunLoopRunSpecific 函数,runloop 参数传入当前 RunLoop 对象,modeName 参数继续传递 CFRunLoopRunInMode 传入的 modeName。也验证了前文文档的解释。

这里还可以看出,虽然 RunLoop 有很多个 mode,但是 RunLoop 在 run 的时候必须只能指定其中一个 mode,运行起来后,被指定的 mode 即为 currentMode。

这 2 个函数都看不出来 RunLoop 是怎么 run 起来的。

接下来我们继续探索一下 CFRunLoopRunSpecific 函数里面都干了什么,看看 RunLoop 具体是怎么 run 的。

CFRunLoopRunSpecific

 * 指定 mode 运行 runloop
 * @param rl 当前运行的 runloop
 * @param modeName 需要运行的 mode 的 name
 * @param seconds  runloop 的超时时间
 * @param returnAfterSourceHandled 是否处理完事件就返回
 */
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 中没有注册任何事情,则就此停止,不进入循环
  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);
  // 取上一次运行的 mode
  CFRunLoopModeRef previousMode = rl->_currentMode;
  // 如果本次 mode 和上一次的 mode 一致
  rl->_currentMode = currentMode;
  // 初始化一个 result 为 kCFRunLoopRunFinished
  int32_t result = kCFRunLoopRunFinished;

  // 1.通知 observer 即将进入 runloop
  if (currentMode->_observerMask & kCFRunLoopEntry) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
  result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  // 10.通知 observer 已经退出 runloop
  if (currentMode->_observerMask & kCFRunLoopExit) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  
  __CFRunLoopModeUnlock(currentMode);
  __CFRunLoopPopPerRunData(rl, previousPerRun);
  rl->_currentMode = previousMode;
  __CFRunLoopUnlock(rl);
  return result;
}

通过 CFRunLoopRunSpecific 的内部逻辑,我们可以得出:

  • 如果指定一个不存在的 mode 来运行 RunLoop,那么会失败,mode 不会被创建,所有这里传入的 mode 必须是存在的
  • 如果指定了一个 mode,但是这个 mode 中不包含人 modeItem,那么 RunLoop 也不会运行,所以必须要传入至少包含一个 modeItem 的 mode
  • 在进入 run loop 之前通知 observer,状态为 kCFRunLoopEntry
  • 在退出 run loop 之后通知 observer,状态为 kCFRunLoopExit

RunLoop 的运行的最核心函数是 __CFRunLoopRun,接下来我们分析 __CFRunLoopRun 的源码。

__CFRunLoopRun

这段代码比较长,请做好心理准备,我已经加了比较详细的注释。本节开头的 run loop 运行步骤 2~9 步都在下面的代码中得到验证。

/**
 *  运行run loop
 *
 *  @param rl              运行的RunLoop对象
 *  @param rlm             运行的mode
 *  @param seconds         run loop超时时间
 *  @param stopAfterHandle true:run loop处理完事件就退出  false:一直运行直到超时或者被手动终止
 *  @param previousMode    上一次运行的mode
 *
 *  @return 返回4种状态
 */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
  // 获取系统启动后的 CPU 运行时间,用于控制超时时间
  uint64_t startTSR = mach_absolute_time();
  // 如果 RunLoop 或者 mode 是 stop 状态,则直接 return,不进入循环
  if (__CFRunLoopIsStopped(rl)) {
    __CFRunLoopUnsetStopped(rl);
    return kCFRunLoopRunStopped;
  } else if (rlm -> _stopped) {
    rlm->_stopped = false;
    return kCFRunLoopRunStopped;
  }
  // mach 端口,在内核中,消息在端口之间传递,初始为0
  mach_port_name_t dispatchPort = MACH_PORT_NULL;
  // 判断是否为主线程
  Boolen libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
  // 如果在主线程 && runloop 是主线程的 runloop && 该 mode 是 commonMode,则给 mach 端口赋值为主线程收发消息的端口
  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) {
    // mode 赋值为 dispatch 端口 _dispatch_runloop_root_queue_perform_4CF
    modeQueuePort = _dispatch_runloop_root_queue_get_port_4FC(rlm->_queue);
    if (!modeQueuePort) {
      CRASH("Unable to get port for run loop mode queue (%d)", -1)
    }
  }
#endif
  // GCD 管理的定时器,用于实现 runloop 的超时机制
  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;
  }
  // seconds 为超时时间,超时执行 __CFRunLoopTimeout 函数
  else if (seconds <= TIMER_INTERVAL_LIMIT) {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_UEUE_PRIOITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
    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(timout_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_IME_FOREVER, 1000ULL);
    dispatch_resume(timeout_timer);
  }
  // 永不超时
  else {
    seconds = 9999999999.0;
    timeout_context->termTSR = UNT64_MAX;
  }
  // 标志位默认为 true
  Boolean didDispatchPortLastTime = true;
  // 记录最后 runloop 状态,用于 return
  int32_t reVal = 0;
  do {
    // 初始化一个存放内核消息的缓冲地
    uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENAT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    match_msg_header_t *msg = NULL;
    mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
    HANDLE livePort = NULL;
    Boolean windowsMessageReceived = false;
#endif
    // 取所需要监听的 port
    __CFPortSet waitSet = rlm->_portSet;
    // 设置 RunLoop 为可以被唤醒状态
    __CFRunLoopUnsetIgnoreWakeUps(rl);
  
    // 2.通知 observer,即将触发 timer 回调,处理 timer 事件
    if (rlm->_observerMask & kCFRunLoopBeforeTimes) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    // 3.通知 observer,即将触发 Source0 回调
    if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    // 执行加入当前 runloop 的 block
    __CFRunLoopDoBlocks(rl, rlm);

    // 4. 处理 source0 事件
    // 有事件处理返回 true,没有事件返回 false
    Boolean sourceHandleThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    if (sourceHandledThisLoop) {
      // 执行加入当前 runloop 的 block
      __CFRunLoopDoBlocks(rl, rlm);
    }
    // 如果没有 Sources0 事件处理并且没有超市,poll 为 false
    Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    // 第一次 do...while 循环不会走该分支,因为 didDispatchPortLastTime 初始化是 true
    if (MACH_PORT_NULL !+ dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
      //从缓冲区读取消息
      msg = (mach_msg_header_t *)msg_buffer;
      // 5. 接收 dispatchPort 端口消息,(接收 source1 事件)
      if(__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
        // 如果收到消息的话,前往第 9 步开始处理 msg
        goto handle_msg;
      }
#elif DEPLOYMENT_TARGET_WINDOWS
      if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
        goto handle_msg;
      }
#endif
    }
    didDispatchPortLastTime = false;
    // 6. 通知观察者 RunLoop 即将进入休眠
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    // 设置 RunLoop 为休眠zhuangt
    __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);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    // 这里有个内循环,用于接收等待端口的消息
    // 进入此循环后,线程进入休眠,直到收到新消息才跳出循环,继续执行 run loop
    do {
      if (kCFUseCollectableAllocator) {
        objc_clear_stack(0);
        memset(msg_buffer, 0, sizeof(msg_buffer));
      }
      msg = (mach_msg_header_t *)msg_buffer;
      // 7.接收 waitSet 端口的消息
      __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0: TIMEOUT_INFINTY);
      // 收到消息之后,livePort 的值为 msg->msgh_local_port
      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) {
          rlm->_timerFired = false;
          break;
        } else {
          if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        }
      } else {
        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);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
    // hear, use the app-suplied 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, &windowsMessageRecieved);
#endif
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    // 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);
    // 取消 runloop 的休眠状态
    __CFRunLoopUnsetSleeping(rl);
    // 8.通知观察者 runloop 被唤醒
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
      __CFRunLoopDoObserver(rl, rlm, kCFRunLoopAfterWaiting);
    // 9.处理收到的消息
handle_msg:;
    __CFRunLoopSetIgnoreWakeUps(rl);
#if DELOYMENT_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(rml);
      __CFRunLoopUnsetSleeping(rl);
    }
#endif
    if (MACH_PORT_NULL == livePort) {
      CFRUNLOOP_WAKEUP_FOR_NOTHING();
      // 通过 CFRunLoopWake 唤醒
    } else if (livePort == rl->_wakeUpPort) {
      // 什么都不干,跳回 2 重新循环
#if DEPLOYMENT_TARGET_WINDOWS
      ResetEvent(rl->_wakeUpPort);
#endif
    }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    // 如果是定时器事件
    else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
      CFRUNLOOP_WAKEUP_FOR_TIMER();
      // 9.1 处理 timer 事件
      if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
        __CFArmNextATimerInMode(rlm, rl);
      }
    }
#endif
#if USE_MK_TIMER_TOO
    // 如果是定时器事件
    else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
      CFRUNLOOP_WAKEUP_FOR_TIMER();
      // 9.1 处理 timer 事件
      if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
        __CFArmNextTimerInMode(rlm, rl);
      }
    }
#endif
    // 如果是 dispatch 到 main queue 的 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
      // 9.2 执行 block
      __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
      _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
      __CFRunLoopLock(rl);
      __CFRunLoopModeLock(rlm);
      sourceHandledThisLoop = true;
      didDispatchPortLastTime = true;
    } else {
      CFRUNLOOP_WAKEUP_FOR_SOURCE();
      CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
      // 有 source1 事件待处理
      if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *reply = NULL;
        // 9.2 处理 source1 事件
        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 DEPLOYMENTA_TARGET_WINDOWS
        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
      }
    }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
    __CFRunLoopDoBlocks(rl, rlm);
    if (sourceHandledThisLoop && stopAfterHandle) {
      // 进入 run loop 时传入的参数,处理完事件就返回
      retVal = kCFRunLoopRunHandledSource;
    } else if (timeout_context->termTSR < mach_absolute_time()) {
      // run loop 超时
      retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {
      // run loop 被手动终止
      __CFRunLoopUnsetStopped(rl);
      retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
      // mode 被终止
      rlm->_stopped = false;
      retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
      // mode 中没有要处理的事件
      retVal = kCFRunLoopRunFinished;
    }
    // 除了上面这几种情况,都继续循环
  } while (0 == retVal);
  if (timeout_timer) {
    dispatch_source_cancel(timeout_timer);
    dispatch_release(timeout_timer);
  } else {
    free(timeout_context);
  }
  return retVal;
}

__CFRunLoopServiceMachPort

第7步调用了 __CFRunLoopServiceMachPort 函数,这个函数在 run loop 中起到了至关重要的作用,下面给出了详细注释。

/**
 *  接收指定内核端口的消息
 *
 *  @param port        接收消息的端口
 *  @param buffer      消息缓冲区
 *  @param buffer_size 消息缓冲区大小
 *  @param livePort    暂且理解为活动的端口,接收消息成功时候值为msg->msgh_local_port,超时时为MACH_PORT_NULL
 *  @param timeout     超时时间,单位是ms,如果超时,则RunLoop进入休眠状态
 *
 *  @return 接收消息成功时返回true 其他情况返回false
 */
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {      /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;  //消息头的标志位
        msg->msgh_local_port = port;  //源(发出的消息)或者目标(接收的消息)
        msg->msgh_remote_port = MACH_PORT_NULL; //目标(发出的消息)或者源(接收的消息)
        msg->msgh_size = buffer_size;  //消息缓冲区大小,单位是字节
        msg->msgh_id = 0;  //唯一id
       
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        
        //通过mach_msg发送或者接收的消息都是指针,
        //如果直接发送或者接收消息体,会频繁进行内存复制,损耗性能
        //所以XNU使用了单一内核的方式来解决该问题,所有内核组件都共享同一个地址空间,因此传递消息时候只需要传递消息的指针
        ret = mach_msg(msg,
                       MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
                       0,
                       msg->msgh_size,
                       port,
                       timeout,
                       MACH_PORT_NULL);
        CFRUNLOOP_WAKEUP(ret);
        
        //接收/发送消息成功,给livePort赋值为msgh_local_port
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        
        //MACH_RCV_TIMEOUT
        //超出timeout时间没有收到消息,返回MACH_RCV_TIMED_OUT
        //此时释放缓冲区,把livePort赋值为MACH_PORT_NULL
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        
        //MACH_RCV_LARGE
        //如果接收缓冲区太小,则将过大的消息放在队列中,并且出错返回MACH_RCV_TOO_LARGE,
        //这种情况下,只返回消息头,调用者可以分配更多的内存
        if (MACH_RCV_TOO_LARGE != ret) break;
        //此处给buffer分配更大内存
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

小结

RunLoop 实际很简单,它是一个对象,它和线程是一一对应的,每个线程都有一个对应的 RunLoop 对象,主线程的 RunLoop 会在程序启动时自动创建,子线程需要手动获取来创建。

RunLoop 运行的核心是一个 do..while.. 循环,遍历所有需要处理的事件,如果有事件处理就让线程工作,没有事件处理则让线程休眠,同时等待事件到来。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 转自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_阅读 1,325评论 0 5
  • RunLoop 的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线...
    Mirsiter_魏阅读 614评论 0 2
  • RunLoop简介 从字面意思来看是运行循环,在程序运行过程中循环做一些事情,如果没有Runloop程序执行完毕就...
    一直很安静_25ae阅读 402评论 0 0
  • 转载:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling阅读 1,434评论 0 13
  • RunLoop的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程...
    IOS学渣阅读 455评论 1 4