iOS - RunLoop
参考链接
一、RunLoop
目的是让线程在有工作的时候忙,在没有工作时让线程进入睡眠状态
所以一个 RunLoop 对象和 Thread 一一对应
本质是一个事件处理的循环
二、主线程 & 辅助线程
- 每个线程都会有一个关联的 RunLoop 对象
- 主线程在应用程序启动时,就会在主线程上添加 RunLoop 并运行
- 在辅助线程中,需要主动调用
run()
开启循环
在 CFRunLoop 源码中,
从 _CFRunLoopGet0(pthread_t t)
方法,可以通过线程获取 RunLoop;
从下面的源码
- __CFRunLoops 是一个字典,用来保存 Thread 和 RunLoop 的对应关系
- __CFRunLoops 的创建,同时对 Main Thread 和 Main RunLoop 对应关系的保存
- __CFRunLoops, Thread Pointer 作为 key,RunLoop 作为 value
/// __CFRunLoops
static CFMutableDictionaryRef __CFRunLoops = NULL;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
/* NilPthreadT */
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
// __CFRunLoops 懒加载
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建 MainThread 的 RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存 MainThread 指针作为 key,RunLoop 为 value
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
// 通过 Thread 指针获取 RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
/* Create RunLoop */
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
三、RunLoop 的运行结构
1. Source
RunLoop 接收来自两种事件的源
>> Input Sources
~~ 通常来自其他线程或另外的应用程序的异步事件,异步事件处理,如果实现 runUntilDate:
RunLoop 会到点退出
[1] Port-based(Mach Ports)
- 已自动由系统内核添加, 通常不需要直接添加 input source ,一般是新建一个 NSPort 添加到 RunLoop 中,该 port 自动创建和配置所需要的 input source
[2] Custom Input Source
必须手动在其他线程添加
Perform Selector Sources
一种自定义的 Custom Imput Source,在执行完毕之后会被移除
-
performSelector:onThread:withObject:waitUntilDone:
和performSelector:onThread:withObject:waitUntilDone:modes:
在Default Mode
或者Specify Mode
添加Source1
,在下一次 RunLoop 循环中执行 Selector,并退出 RunLoop -
performSelector:withObject:afterDelay:
和performSelector:withObject:afterDelay:inModes:
在Default Mode
或者Specify Mode
添加Timer Source
,在下一次 RunLoop 循环中执行 Selector,并退出 RunLoop
>> Timer Sources
-
已计划事件或者重复事件间隔的同步事件,不会造成 RunLoop 退出
-
计时器并不是使用的实时机制
- 因为计时器 Timer Source 会添加到指定的 Mode,RunLoop 也只会执行一个 Mode,如果当前 RunLoop 没有执行该 Mode,则 Timer Source 不会触发
- 如果 Timer 在 RunLoop 执行处理过程中触发,则需要等下一次循环
~~ 计时器有一次或者重复发生事件,重复计时器会根据计划的触发时间(而不是实际的触发时间)自动重新计划自己的时间;例如如果将计时器在特定时间触发,并且此后每 5 秒钟触发一次,则即使实际触发时间被延迟,计划的触发时间也将始终落在原始的 5 秒时间间隔上。如果触发时间延迟得太多,以致于错过了一个或多个计划的触发时间,则计时器将在错过的时间段内触发一次。在错过了一段时间后触发,计时器将重新安排为下一个计划的触发时间
image.png2. Observer
注册 RunLoop.Observer 能够接收 RunLoop 的处理进程,在 RunLoop 循环本身执行期间的特定位置触发
- 进入 RunLoop
- RunLoop 将要处理 Timer Source
- RunLoop 将要处理 Input Source
- RunLoop 即将进入睡眠状态
- RunLoop 被唤醒
- 退出 RunLoop
3. RunLoopMode
是 Input Sources、Timers、Observers 的集合
下面是已定义的 Mode:
Mode | Type | Description |
---|---|---|
NSDefaultRunLoopMode |
Default | 默认模式 |
NSConnectionReplyMode |
Connection | 关联 NSConnection 对象 |
NSModalPanelRunLoopMode |
Modal | 面板事件 |
NSEventTrackingRunLoopMode |
Event tracking | 鼠标拖拽或者其他用户界面跟踪循环 |
NSRunLoopCommonMode |
Common modes | mode group; 默认包括 default mode 和 tracking mode |
也可通过 CFRunLoopAddCommonMode
新建自定义 Mode
4. RunLoop Run
指定一个 Mode Run
4. RunLoop
NSRunLoop 底层由 CFRunLoopRef 实现
5. RunLoop 的整个运行过程
- 通知 Observer 已进入 RunLoop
- 通知 Observer,Timer 即将触发
- 通知 Observer,不基于端口的 Input Source(Source0) 即将触发
- 触发所有准备触发的非基于端口的 Input Source(Source0)
- 如果基于端口的 Input Souce(Source1) 已准备好并等待启动,请立即处理事件,转到 9
- 通知 Observer,线程即将进入睡眠状态
- 使线程进入睡眠状态,直到发生以下事件之一
- 时间到达基于端口的 input Source
- 计时器触发
- 为了运行循环设置的超时值到期
- RunLoop 被指定唤醒
- 通知 Observer,线程刚刚醒来
- 处理未解决的事件
- 如果触发了用户定义的计时器,处理计时器并重启 RunLoop,转 2
- 如果触发了输入源,则传递事件
- 如果 RunLoop 被明确唤醒,但未超时,请重启 RunLoop,转 2
- 通知 Observer,RunLoop 已退出
二、分析 CFRunLoop 源代码
源码中对 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
定义,暂时找不到解释
#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
DEPLOYMENT_TARGET_WINDOWS
为 Window 系统,删除对相关源代码
1 . 首先我们来看下 CFRunLoopRun
函数,可以看出默认是在 Default Mode
下运行,当 CFRunLoopRunResult
为 stopped
或 finished
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
result
保存的类型为 CFRunLoopRunResult
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
kCFRunLoopRunFinished = 1,
kCFRunLoopRunStopped = 2,
kCFRunLoopRunTimedOut = 3,
kCFRunLoopRunHandledSource = 4
};
- 从
CFRunLoopRunSpecific
可以看出RunLoop
需要指定 Mode
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
...
/// 1. 通知 Observer 已进入 RunLoop - __CFRunLoopDoObservers: Entry
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/* __CFRunLoopRun */
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// 10. 通知 Observer,RunLoop 已退出 - __CFRunLoopDoObservers: Exit
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
...
return result;
}
- 从
__CFRunLoopRun
,分为几个部分
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
/// 初始化一个定时器
dispatch_source_t timeout_timer = NULL;
...
// retVal: CFRunLoopRunResult
int32_t retVal = 0;
do {
...
/** __CFRunLoopDoObservers: Before Timers */
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/** __CFRunLoopDoObservers: Before Sources */
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
/** __CFRunLoopDoSources0 */
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
...
// 2. 如果有 Source1 跳转第 9 步, 处理来自主线程的端口消息
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
}
...
/** __CFRunLoopDoObservers: Before Waiting */
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
/** 等待被唤醒 */
...
/** After waiting */
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 9.
} while (0 == retVal)
return retVal;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
/// second 是举例 RunLoop 运行到结束的时间间隔
/// 初始化一个定时器
dispatch_source_t timeout_timer = NULL;
...
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_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
/// 设置定时器触发事件处理回调 __CFRunLoopTimeout 是一个函数指针,通过 RunLoop.wakeUpPort 唤醒
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
/// 设置定时器取消事件回调处理,__CFRunLoopTimeoutCancel 是一个函数指针,对 RunLoop 的释放
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
...
}
等待被唤醒
{
do {
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);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue.
// If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
}
__CFRunLoopServiceMachPort
等待接收消息
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;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
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);
// 接收到消息
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
// 时间超时
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
// 消息过大
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
唤醒后处理事件
{
if (MACH_PORT_NULL == livePort) { /** No Port */
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) { /** WakeUp Port */
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { /** DispatchQueue Port */
// modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
CFRUNLOOP_WAKEUP_FOR_TIMER();
// 处理 rlm -> queue 队列下的 GCD Timer
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { /** Timer Port */
/// MK_Timer
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
// 处理 rlm -> queue 队列下的 GCD Timer
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}else if (livePort == dispatchPort) { /** DispathMainQueue Port */
// 处理 GCD 主线程
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else { /** Source1 */
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
}
}
}
RunLoopMode
Mode | Description |
---|---|
CFRuntimeBase _base | /// |
pthread_mutex_t _lock | /// |
CFStringRef _name | /// |
Boolean _stopped | /// |
char _padding[3] | /// |
CFMutableSetRef _sources0 | No Base Port Input Source |
CFMutableSetRef _sources1 | Base Port Input Source |
CFMutableArrayRef _observers | Observers |
CFMutableDictionaryRef _portToV1SourceMap | Port and Source1 map |
__CFPortSet _portSet | 存储端口;TimerPort、wakeupPort、queuePort |
CFIndex _observerMask | /// |
dispatch_source_t _timerSource | Dispatch Timer Source |
dispatch_queue_t _queue | Run Loop Mode Queue (Handle Timer Source) |
Boolean _timerFired | Dispatch Timer Source 启动标志 |
Boolean _dispatchTimerArmed | /// |
mach_port_t _timerPort | MK_Timer Port |
Boolean _mkTimerArmed | /// |
uint64_t _timerSoftDeadline | /// |
uint64_t _timerHardDeadline | /// |