什么是RunLoop
一般来讲,一个线程一次只能执行一个任务,执行完任务后线程就会退出,避免资源占用。但是某些情况下,我们需要线程执行完毕后不退出,而是处于等待接受事件并处理的常驻状态,例如程序运行期间一直存在的主线程。
这种模式通常称作 Event Loop,在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。
结合苹果在文档里的说明,RunLoop 的概念如下:
run loop
是一个event processing loop
(事件处理循环),用于管理需要处理的事件和消息。run loop的目的在于让线程有任务时处理,无任务时休眠,节省CPU资源,优化性能。
OS X/IOS系统中提供了两个对象实现RunLoop:NSRunLoop
和CFRunLoopRef
CFRunLoopRef
: 存在CoreFoundation框架内,它提供了纯C函数的API,所有这些线程API都是线程安全
的,可以从任何线程调用。
NSRunLoop
: 存在于Foundation,是基于CFRunLoopRef的封装
,提供了面向对象API,但是这些API不是线程安全
的,配置其他线程的runLoop都有可能发生意料之外的结果甚至crash。
CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看。
Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本:https://github.com/apple/swift-corelibs-foundation/,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。)
RunLoop 与线程的关系
获取线程的runLoop:
- CFRunLoop:CFRunLoopGetCurrent()、CFRunLoopGetMain();
- NSRunLoop:[NSRunLoop currentRunLoop]、[NSRunLoop mainRunLoop];
注意:同一个thread线程对应的 NSRunLoop 和 CFRunLoop 引用了同一runLoop。NSRunLoop提供了
getCFRunLoop
方法获取对应的CFRunLoop
它们的底层实现如下:
获取主线程对应的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;
}
获取线程对应的runLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
可以看出,两个函数最终都是调用 _CFRunLoopGet0(pthread_t t),pthread_main_thread_np()获取主线程(实际对外Api为pthread_main_np()),pthread_self()获取当前线程,内部逻辑如下:
全局的Dictionary,key: pthread_t, value: CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
自动锁,用于线程安全
static CFSpinLock_t loopsLock = CFSpinLockInit;
获取线程 pthread_t 对应的 runLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (!__CFRunLoops) {
// 第一次访问CFRunLoops时,初始化全局MutableDictionary,并为主线程创建一个 runLoop
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
}
// 直接从 Dictionary 里获取
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 获取字典_CFRunLoops中 key 为 thread 的 runLoop
if (!loop) {
// 获取不到,为 thread 创建一个runLoop,并保存到_CFRunLoops
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
}
if (pthread_equal(t, pthread_self())) {
/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
以上源码可以看出:
-
RunLoop和线程之间是一一对应的
,其关系保存在一个全局的Dictionary,其中key为p_thread,value为CFRunLoopRef。 - 懒加载创建runLoop。线程刚创建时并没有RunLoop,如果你不主动获取,那它一直不会创建。RunLoop的创建是发生在第一次获取的。销毁发生在线程结束时执行的回调函数__CFFinalizeRunLoop。
- 苹果提供的获取线程的runloop的API只有两个,所以,一个线程只能在自己内部获取其对应runLoop(主线程除外,每个线程都能获取到主线程)
以下为 p_thread 销毁时的回调函数__CFFinalizeRunLoop,目的是清除runLoop上所有的输入源source,可跳过不看:
thread线程销毁时的回调函数
CF_PRIVATE void __CFFinalizeRunLoop(uintptr_t data) {
CFRunLoopRef rl = NULL;
if (data <= 1) {
// 从全局字典 __CFRunLoops 中移除相应thread
if (__CFRunLoops) {
rl = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(pthread_self()));
if (rl) CFRetain(rl);
CFDictionaryRemoveValue(__CFRunLoops, pthreadPointer(pthread_self()));
}
} else {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(data - 1), (void (*)(void *))__CFFinalizeRunLoop);
}
if (rl && CFRunLoopGetMain() != rl) {
// 移除runLoop中Set<Mode>集合里所有mode的source
CFArrayRef array = CFRunLoopCopyAllModes(rl);
for (CFIndex idx = CFArrayGetCount(array); idx--;) {
CFStringRef modeName = (CFStringRef)CFArrayGetValueAtIndex(array, idx);
__CFRunLoopRemoveAllSources(rl, modeName);
}
// 移除mode中Set<commonModes>所有的 source
__CFRunLoopRemoveAllSources(rl, kCFRunLoopCommonModes);
CFRelease(array);
}
if (rl) CFRelease(rl);
}
RunLoop相关类
在CoreFoundation中关于RunLoop有5个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
它们的关系如下:
![](https://img.haomeiwen.com/i11738566/e586419f975aed51.png)
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer。
CFRunLoopSourceRef是事件产生的地方。它有两个版本:Source0和Source1
-
Source0
:处理App内部事件,App自己负责管理,不能主动触发事件,需要调用CFRunLoopSourceSignal(source)将source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒 RunLoop,让其处理这个事件如UIEvent、CFSocket -
Source1
:由RunLoop和内核管理,mach_port驱动,用于通过内核和其他线程发送消息,能主动唤醒RunLoop的线程,如CFMachPort、CFMessagePort。
具体内部结构如下,可跳过不看:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; source0只有在被标记为Signaled状态,才会被处理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; source0
CFRunLoopSourceContext1 version1; source1
} _context;
};
source0:
typedef struct {
CFIndex version; // 区分source0和source1,0:source0, 1:source1
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); 移除source的回调函数,
void (*perform)(void *info); source0事件发生的回调函数
} CFRunLoopSourceContext;
source1:
typedef struct {
CFIndex version; 区分source0和source1,0:source0, 1:source1
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);
mach_port_t (*getPort)(void *info); 函数指针,返回source1对应的port端口
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info); 回调函数,source1事件发生时触发
} CFRunLoopSourceContext1;
-
Port-Base Sources(基于端口souce1)
:Cocoa和Core Foundation提供内置支持。在Cocoa中只需创建一个port
对象,并使用NSPort
的方法将该端口添加到runLoop中,port对象自动为您处理所需source的创建和配置。在CoreFoundation中,必须手动创建port及其runLoop source
CFRunLoopObserverRef是runLoop的观察者,内部包含观察的runLoop,以及runLoop状态发生变化时的回调指针,只有CoreFoundation框架提供了创建observer的API。
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; runLoop发生变化时执行的回调函数
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
// CFRunLoopObserverCallBack是函数指针
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
runLoopObserver观察的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 即将进入runLoop
kCFRunLoopBeforeTimers = (1UL << 1), 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), 从休眠中唤醒
kCFRunLoopExit = (1UL << 7), 即将退出 runLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
CFRunLoopTimerRef是基于时间的触发器,包含了时间长度和回调函数,当 timer 加入 RunLoop 后,RunLoop 会注册对应的时间点,在时间点上,RunLoop 会被唤醒执行 timer 中的函数回调,结构如下:
CFRuntimeBase _base;
uint16_t _bits; 标记timer执行的状态
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; timer所在的runLoop
CFMutableSetRef _rlModes; timer所在的runLoopMode
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; 触发时间 fire Time
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; 回调函数
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。
CFRunLoopMode
mode是source/timer/observer的容器,三者被统称为 mode item,结构大致如下:
struct __CFRunLoopMode {
CFStringRef _name; 唯一标识符
Boolean _stopped;
CFMutableSetRef _sources0; Set(source0)
CFMutableSetRef _sources1; Set(source0)
CFMutableArrayRef _observers; Array(source0)
CFMutableArrayRef _timers; Array(source0)
...
}
自定义Model只需要指定任意的名称即可
,但是必须确保添加一个或多个Source/Timer/Observer到Mode中。
但是苹果并没有暴露创建runLoopMode的API,只给出了如下接口:
管理Mode的接口
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName)
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
管理mode item 的接口 (source/timer/observer)
以下接口功能大同小异,分别为:是否包含 mode item、添加item、移除item
Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef modeName);
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef modeName);
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef modeName);
以上函数,我们只能通过参数 mode name操作mode。
然而事实上当你传入一个新的 mode name 时,如果 RunLoop内部没有对应mode时,RunLoop会自动帮你创建对应的CFRunLoopModeRef,其底层调用函数是__CFRunLoopFindMode(runloop, modeName, create),内部逻辑如下:
1.runLoop中查找指定name的 Mode
2.没找到,是否自动创建名为mode name 的Mode,若否返回NULL,否则继续向下执行
3.创建一个的Mode,_name为传入的参数名mode name
4.初始化Mode中各个成员变量
5.将Mode添加到runLoop的_modes集合中
代码大致实现如下,可跳过不看:
/* call with rl locked, returns mode locked */
/**
在runloop中查找指定 mode name的 Mode
@param rl 待搜索的runLoop
@param modeName 指定mode的名称
@param create 如果runLoop中不存在指定mode,是否创建一个
*/
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
// 1.runLoop中查找指定 Mode
CFRunLoopModeRef rlm;
rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
if (NULL != rlm) { return rlm; }
// 2.没找到,是否自动创建名称为mode name 的Mode
if (!create) { return NULL;
// 3.创建一个指定名称的Mode
rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
if (NULL == rlm) {
return NULL;
}
// 4.初始化runLoopMode内部成员变量值
__CFRunLoopLockInit(&rlm->_lock);
rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
rlm->_stopped = false;
rlm->_portToV1SourceMap = NULL;
rlm->_sources0 = NULL;
rlm->_sources1 = NULL;
rlm->_observers = NULL;
rlm->_timers = NULL;
rlm->_observerMask = 0;
rlm->_portSet = __CFPortSetAllocate();
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
// 通过GCD创建timer定时器
#if USE_DISPATCH_SOURCE_FOR_TIMERS
rlm->_timerFired = false;
rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue", 0);
mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***", -1);
rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, rlm->_queue);
__block Boolean *timerFiredPointer = &(rlm->_timerFired);
dispatch_source_set_event_handler(rlm->_timerSource, ^{
*timerFiredPointer = true;
});
_dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
dispatch_resume(rlm->_timerSource);
#endif
// 通过XNU内核创建timer定时器
#if USE_MK_TIMER_TOO
rlm->_timerPort = mk_timer_create();
#endif
ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
CFSetAddValue(rl->_modes, rlm);
return rlm;
}
CFRunLoopRef
CFRunLoopRef是执行Loop的类型,包含若干mode,每次运行Loop只能指定其中一个Mode(保存为_currentMode),仅监听和传递与该Model相关的Source/Timer/Observer,防止互相干扰
。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。大致结构如下:
struct __CFRunLoop {
__CFPort _wakeUpPort; 用于唤醒线程的port,调用CFRunLoopWakeUp(runLoop)
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; runLoop对应的线程
CFMutableSetRef _commonModes; 通用Set(Model)
CFMutableSetRef _commonModeItems; 通用Set(Source/Observer/Timer)
CFRunLoopModeRef _currentMode; 当前的Mode
CFMutableSetRef _modes; Set(Model)
};
_commonModes保存common标记的Modes;
_commonModeItems保存common标记的source/timer/observer;
每当RunLoop发生变化时,就会将_commonModeItems里的内容添加到_commonModes内部每个Mode里。
当Mode以Common
形式标记自己并添加到runLoop上时,实际是将runLoopMode的_name
添加到RunLoop的_commonModes
。
RunLoop内部逻辑
根据苹果文档说明,RunLoop内部逻辑大致如下:
1. Notify observers that the run loop has been entered.
2. Notify observers that any ready timers are about to fire.
3. Notify observers that any input sources that are not port based are about to fire
4. Fire any non-port-based input sources that are ready to fire.
5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.
6. Notify observers that the thread is about to sleep.
7. Put the thread to sleep until one of the following events occurs:
- An event arrives for a port-based input source.
- A timer fires.
- The timeout value set for the run loop expires.
- The run loop is explicitly woken up.
8. Notify observers that the thread just woke up.
9. Process the pending event.
- If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.
- If an input source fired, deliver the event.
- If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
10.Notify observers that the run loop has exited.
这里附上网图,更清晰:
![](https://img.haomeiwen.com/i11738566/df7a439f3d6cdec9.png)
由于对Timer和Source的监听通知的传递是在事件发生之前,因此通知时间和实际事件的时间可能存在差异
内部代码大致如下:
//使用defaultMode启动runLoop
void CFRunLoopRun(void) {
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
//指定mode启动runLoop,并设置runLoop超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
// runLoop实现
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 根据 modeName 找到对应的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// mode不存在,或mode里没有source/timer,直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// 替换runLoop当前的mode,runLoop一次只能执行一个mode
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
// 1.通知observer,runLoop 即将进入Loop循环
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 进入Loop循环
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 10.通知observer,runLoop 即将退出
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
// 指定model执行完毕后_currentMode替换为上一次mode
rl->_currentMode = previousMode;
return result;
}
// runLoop循环体
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
do {
// 2.通知runLoop,即将触发Timer回调
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3.通知runLoop,即将触发source0回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// 4.触发source0回调
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// 5.如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去 步骤9 处理消息
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)
if (hasMsg) goto handle_msg;
}
// 6.通知observer,runloop的线程即将进入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting))
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 线程进入休眠
__CFRunLoopSetSleeping(rl);
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒:
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY) {
mach_msg(msg, MACH_RCV_MSG, 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
}
// 唤醒runLoop
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);
// 8.通知observer,runLoop的线程刚刚被唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
// 收到消息,处理消息
// 9.1 如果是一个 Timer 到时间了,触发这个timer的回调(timer可能来自GCD的dispatch_source_或者XNU的mk_timer创建的计时器)
if (msg_is_timer) {
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
// 9.2 如果有dispatch到main_queue的block,执行block
else if (msg_is_dispatch_main_queue) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
}
// 执行加入到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)) {
// 被外部调用者强制停止了,即调用CFRunLoopStop(runloop)
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
// 外部调用强制停止
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// mode中 source/timer/observer为空
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
以下几个函数贯穿runLoop回调过程:
void __CFRunLoopDoObservers()
Boolean __CFRunLoopDoBlocks()
Boolean __CFRunLoopDoTimers()
Boolean __CFRunLoopDoSources0()
Boolean __CFRunLoopDoSource1()
这些函数内部最终调用如下函数:
void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void))
void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info)
void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info)
void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__( void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info)
void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg)
因此runLoop的执行过程也可大致表示如下:
int32_t __CFRunLoopRun() {
/// 1. 通知Observers,即将进入RunLoop
/// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. 通知 Observers: 即将触发 Timer 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 触发 Source0 (非基于port的) 回调。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即将进入休眠
/// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. 通知Observers,线程被唤醒
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. 如果是被Timer唤醒的,回调Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (1);
/// 10. 通知Observers,即将退出RunLoop
/// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
RunLoop 的底层实现
RunLoop的核心是基于mach port的,其进入睡眠时调用的函数时mach_msg()。
下面稍微介绍一个OSX/iOS的系统架构:
![](https://img.haomeiwen.com/i11738566/53589e3e48493da6.png)
应用层包括用户能接触到的图形应用,例如 Spotlight、Aqua、SpringBoard 等。
应用框架层即开发人员接触到的 Cocoa 等框架。
核心框架层包括各种核心框架、OpenGL 等内容。
Darwin 即操作系统的核心,包括系统内核、驱动、Shell 等内容,这一层是开源的,其所有源码都可以在 opensource.apple.com 里找到。
我们在深入看一下 Darwin 这个核心的架构:
![](https://img.haomeiwen.com/i11738566/44ac9b61ee667a0f.png)
XNU是Darwin的内核
,它是“X is not UNIX”的缩写,是一个混合内核
,由 Mach
微内核、 BSD
、IOKit
(还包括一些上面没标注的内容)组成。Mach
被称作XNU 内核的内环 ,其作为一个微内核,只能完成操作系统最基本的职责,比如:进程和线程、虚拟内存管理、任务调度、IPC(进程通信)和消息传递机制。BSD
层可以看作围绕 Mach 层的一个外环,其提供了诸如进程管理、文件操作、设备访问网络等功能。IOKit
层是为设备驱动提供了一个面向对象(C++)的一个框架。
Mach 本身提供的 API 非常有限,而且苹果也不鼓励使用 Mach 的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。在 Mach 中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”。和其他架构不同, Mach 的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信
。”消息”是 Mach 中最基础的概念,消息在两个端口 (port) 之间传递,这就是 Mach 的 IPC (进程间通信) 的核心。
Mach的消息结构如下:
typedef struct{
mach_msg_header_t header;
mach_msg_body_t body;
} mach_msg_base_t;
typedef struct {
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
一条 Mach 消息实际上就是一个二进制数据包 (BLOB),其头部定义了当前端口 local_port 和目标端口 remote_port,
发送和接受消息是通过同一个 API 进行的,其 option 标记了消息传递的方向:
mach_msg_return_t mach_msg_overwrite(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify,
mach_msg_header_t *rcv_msg,
mach_msg_size_t rcv_limit);
为了实现消息的发送和接收,mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态
;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图
![](https://img.haomeiwen.com/i11738566/86d90a819e1f6475.png)
这些概念可以参考维基百科: System_call、Trap_(computing)。
RunLoop 的核心就是一个 mach_msg() (见上面代码的第7步),RunLoop 调用这个函数去接收消息,如果没有发送 port 消息过来,内核会将线程置于等待状态。可以任意跑起一个app,然后在 App 静止时点击暂停,可以看到主线程调用栈是停留在 mach_msg_trap() 这个地方:
![](https://img.haomeiwen.com/i11738566/293a3d779414248f.png)
关于具体的如何利用 mach port 发送信息,可以看看 NSHipster 这一篇文章,或者这里的中文翻译 。
什么时候使用RunLoop
IOS的UIApplication和OS X的NSApplication自动为主线程创建并启动了runLoop
,无需我们关心。而其他线程是否需要runLoop需要我们自行判断。通常情况下,如果只是使用线程执行长时间且预置的任务时并不需要runLoop。RunLoop更适用于需要与线程进行更多交互的情况,例如:
- 使用
ports
或自定义输入源
与其它线程进行通信. - 使用计时器
Timer
- Cocoa应用程序使用任何
performSelector...
方法 -
保持线程
以执行定期任务
如果runLoop内没有source或者repeat执行timer等待监听,则runLoop立即就会退出销毁
退出RunLoop
- 传入超时时间
CFRunLoopStop(CFRunLoopRef rl)
定时器
关于定时器,CFRunLoop.c中同时包含了基于GCD和mk_timer的定时器,分别用如下宏处理相关操作:
#if USE_DISPATCH_SOURCE_FOR_TIMERS // GCD
/*处理 GCD timer相关操作*/
#endif
#if USE_MK_TIMER_TOO // mk_timer
/*处理 mk_timer 相关操作*/
#endif
在CFRunLoopModeRef中对应不同的timer,成员变量也是不一样的:
struct __CFRunLoopMode {
#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 USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
};
将 CFRunLoopRef 和 CFRunLoopMode 的 struct 拷贝到代码中,并添加一个NSTimer到runLoop中运行查看runLoopMode的实际成员变量值:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoop {
...
};
struct __CFRunLoopMode {
...
};
- (void)viewDidLoad {
[super viewDidLoad];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 repeats:YES block:^(NSTimer * _Nonnull timer) {
CFRunLoopRef cfrunLoop = CFRunLoopGetCurrent();
NSLog(@"ddd");
}];
}
断点查看cfrunloop的_currentMode中成员变量
![](https://img.haomeiwen.com/i11738566/b9f1596bc8581cf1.png)
可见,添加到RunLoop的timer只有成员变量_timerPort和_mkTimerArmed,所以NSTimer底层的CFRunLoopTimerRef,是用XUN内核的mk_timer创建以及驱动的
。
默认情况下,timer添加到runLoopMode上标记为RunLoopDefaultMode
,当scrollView滑动时,RunLoop的currentMode切换为NSEventTrackingRunLoopMode
,此时计时器是不执行的(即runLoop的_currentMode不是Timer所关联的mode类型,则Timer不执行)。
除此之外,当某个预设时间点到达后,若runLoop正在执行一个长任务,则该时间点的回调被跳过,直到下一个Loop循环再执行。如果延迟太多以至错过一个或多个Timer预设执行时间,则Timer在错过的时间段内仅回调执行一次。
这就导致了NSTimer并不是实时的,无法保证在非常准确的时间点回调
。
关于这点,苹果在文档里也是提到了:
Although it generates time-based notifications, a timer is not a real-time mechanism. Like input sources, timers are associated with specific modes of your run loop. If a timer is not in the mode currently being monitored by the run loop, it does not fire until you run the run loop in one of the timer’s supported modes. Similarly, if a timer fires when the run loop is in the middle of executing a handler routine, the timer waits until the next time through the run loop to invoke its handler routine. If the run loop is not running at all, the timer never fires.
所以在对计时器的精确要求较高的地方
- 应当将NSTimer添加到标记
RunLoopCommonModes
的runLoopMode上,标记为common的mode会同步内容到runLoop所有的mode上,这样即使是UITrackingRunLoopMode也会执行timer
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; - 或者使用不受runLoopMode影响的基于GCD的计时器:dispatch_source_t timer
PerformSelector
调用NSObject的performSelector:with:afterDealy:后,实际上其内部会创建一个Timer并添加到当前线程的RunLoop中,所以如果当前线程没有RunLoop,则这个方法会失效。
override func viewDidLoad() {
super.viewDidLoad()
perform(#selector(sing), with: nil, afterDelay: 3)
}
@objc func sing() {
print("我家住在黄土高坡哦~~~")
}
![](https://img.haomeiwen.com/i11738566/93eeb7fac07669c0.png)
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
App启动后RunLoop的内容:
- (void)viewDidLoad {
[super viewDidLoad];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSLog(@"Runloop:{\n %@ \n}",runloop);
}
Runloop:{
current mode = kCFRunLoopDefaultMode
common modes = {
UITrackingRunLoopMode
kCFRunLoopDefaultMode
}
common mode items = {
// Source0
CFRunLoopSource { order = -1, context = { callout = PurpleEventSignalCallback()}}
CFRunLoopSource { order = -2, context = { callout = __handleHIDEventFetcherDrain()}}
CFRunLoopSource { order = -1, context = {callout = __handleEventQueue()}}
CFRunLoopSource { order = 0, context = {callout = FBSSerialQueueRunLoopSourceHandler()}}
// Source1(mach port)
CFRunLoopSource { order = -1, context = { callout = PurpleEventCallback()}}
CFRunLoopSource { order = 0}}
CFRunLoopSource { order = 0}}
CFRunLoopSource { order = 0, context = { callout = _ZL27change_notify_port_callbackP12__CFMachPortPvlS1_()}}
// Observer
CFRunLoopObserver {activities = 0xa0, order = 2001000, callout = _afterCACommitHandler()}
CFRunLoopObserver {activities = 0x20, order = 0, callout = _UIGestureRecognizerUpdateObserver()}
CFRunLoopObserver {activities = 0xa0, order = 1999000, callout = _beforeCACommitHandler()}
CFRunLoopObserver {activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler()}
CFRunLoopObserver {activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler()}
CFRunLoopObserver {activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()}
},
modes = {
CFRunLoopMode {
name = UIInitializationRunLoopMode,
...
},
CFRunLoopMode {
name = UITrackingRunLoopMode,
sources0 = { /* same as 'common mode items' */},
sources1 = { /* same as 'common mode items' */},
observers = { /* same as 'common mode items' */},
timers = (null),
},
CFRunLoopMode {
name = GSEventReceiveRunLoopMode
sources0 = {
CFRunLoopSource {order = -1, context = CFRunLoopSource context {callout = PurpleEventSignalCallback()}}
},
sources1 = CFBasicHash {
CFRunLoopSource {order = -1, context = CFRunLoopSource context {callout = PurpleEventCallback()}}
},
observers = (null),
timers = (null),
},
CFRunLoopMode {
name = kCFRunLoopDefaultMode
sources0 = { /* same as 'common mode items' */},
sources1 = { /* same as 'common mode items' */},
observers = { /* same as 'common mode items' */},
timers = CFArray {
CFRunLoopTimer {
firing = No, interval = 0, tolerance = 0, next fire date = 555867417 (-13.3055891 @ 4516020992225), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (UIKit.framework/UIKit)})
}},
},
CFRunLoopMode {
name = kCFRunLoopCommonModes,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
},
}
}
可以看到,系统默认注册了5个Mode:
1.UIInitializationRunLoopMode
: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
2.UITrackingRunLoopMode
: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
3.GSEventReceiveRunLoopMode
: 接受系统事件的内部 Mode,通常用不到。
4.kCFRunLoopDefaultMode
: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
5.kCFRunLoopCommonModes
: 这是一个占位的 Mode,没有实际作用。
苹果只暴露的 DefaultMode 和 CommonMode
你可以在这里看到更多的苹果内部的 Mode,但那些 Mode 在开发中就很难遇到了
AutoreleasePool
添加到autoreleasePool的对象会被延迟释放,需要等到pool释放时才会对内部保存的对象发送release消息。
那Autorelease什么时候释放?
上面我们已经看到过,App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
Runloop:{
...
common mode items = {
CFRunLoopObserver {activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler()}
CFRunLoopObserver {activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler()}
...
},
第一个 Observer的activities = 0x1= kCFRunLoopEntry
监视的事件: kCFRunLoopEntry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高
,保证创建释放池发生在其他所有回调之前。
第二个 Observer 的activities = 0xa0 = kCFRunLoopBeforeWaiting & kCFRunLoopExit
监视了两个事件:
1.BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
2.Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。
这个 Observer 的 order 是 2147483647,优先级最低
,保证其释放池子发生在其他所有回调之后。
事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为__IOHIDEventSystemClientQueueCallback()
。
事件的产生和传递大致如下:
-
当一个硬件事件(触摸/锁屏/摇晃等)发生后,由
IOKit.framework
生成一个IOHIDEvent
事件
(其中IOKit
是苹果的硬件驱动框架, 由它进行底层接口的抽象封装与系统进行交互传递硬件感应的事件。它专门处理用户交互设备,由IOHIDServices
和IOHIDDisplays
两部分组成,其中IOHIDServices
是专门处理用户交互的,它会将事件封装成IOHIDEvents
对象,详细请看这里) -
事件产生后并由 SpringBoard接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event。
-
接着用
mach port
转发给需要的App进程。 -
随后IOS注册的
Source1
接收到IOHIDEvent,在回调__IOHIDEventSystemClientQueueCallback 内触发source0
,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。 -
_UIApplicationHandleEventQueue()
会把IOHIDEvent
处理并包装成UIEvent
进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。触摸事件的调用栈如下:
点击事件的调用栈
以上为各参考文献所注,大同小异,然而笔者亲自触发了一个触摸事件后调用栈的结构如下,只看到了__handleEventQueueInternal和__dispatchPreprocessedEventQueue,而且App启动时并没有观察到__IOHIDEventSystemClientQueueCallback()的source1,希望有知道的大佬能为我解惑一下,谢谢!
![](https://img.haomeiwen.com/i11738566/59049517b1ad7211.png)
手势识别
当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
参考:
iOS线下分享《RunLoop》by 孙源@sunnyxx
深入理解RunLoop
iOS 事件处理机制与图像渲染过
黑幕背后的Autorelease
OS X Internal: A System
网友评论