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描述):
整个流程并不复杂(需要注意的就是黄色区域的消息处理中并不包含source0,因为它在循环开始之初就会处理),整个流程其实就是一种Event Loop的实现,其他平台均有类似的实现,只是这里叫做RunLoop。
RunLoop与线程的关系如下图
图中展现了 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 互不影响。
如图所示,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 事件,具体流程如下图。
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 下运行当前线程的 RunLoopCFRunLoopRunResult 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.. 循环,遍历所有需要处理的事件,如果有事件处理就让线程工作,没有事件处理则让线程休眠,同时等待事件到来。