本文主要内容:
- 概念
- 数据结构
- 事件循环的实现机制
- RunLoop与NSTimer
- RunLoop与线程
- 源码
一、概念
RunLoop是通过内部维护的事件循环来对事件和消息进行管理的一个对象。
二、数据结构
OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
1.CFRunLoop
- pthread,runloop与线程是一一对应的关系。
- currentMode,是一个CFRunLoopMode的一种数据,结构,表示当前runloop是那种model。
- modes,NSMutableSet<CFRunLoopMode *>,是一个model集合。
- commonModes,,NSMutableSet<NSString *>,是一个字符串的集合。这个mode会在下边CFRunLoopMode具体说明。
- commonModeItes,也是个集合,集合中的内容是添加到当前runloop不同mode中的所有items:source、timer、observer。
2.CFRunLoopMode
- name,实质是字符串,不同mode的名字。
-
sources0、sources1,都是MutableSet集合类型。区别,如下图:
- observers和timers,都是数组类型。
3.Source/Timer/Observer
- CFRunLoopTimer,基于事件的定时器,和NSTimer是toll-free bridge的(免费桥转换)。
- CFRunLoopObserver,我们可以通过注册一些observer来实现对runloop的相关时间点的监测。
观测的时间点:
/* 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
};
4.RunLoop的mode
RunLoop与mode的关系是一对多的关系,mode与timer、source、observer也是一对多的关系:
同一时间RunLoop只能在一种mode下运行,如果需要切换mode,需要停止当前RunLoop,然后重启RunLoop。
model 主要是用来指定事件在运行循环中的优先级的,分为:
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
- UITrackingRunLoopMode:ScrollView滑动时
- UIInitializationRunLoopMode:启动时
- NSRunLoopCommonModes(kCFRunLoopCommonModes):commonModes并不是一个实际存在的mode(CFRunLoopMode),而是字符串的集合,它是同步timer、observer、source到多个mode的一种技术方案。
苹果公开给我们的有两个:
NSDefaultRunLoopMode和NSRunLoopCommonModes。
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
三、事件循环的实现机制
四、RunLoop与NSTimer
timer默认被添加到RunLoop的NSDefaultRunLoopMode中,tableview滑动时,RunLoop的mode会从kCFRunLoopDefaultMode切换到UITrackingRunLoopMode,此时定时器就失效了。
解决方案:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
实际是通过函数:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName);
五、RunLoop与线程
- 线程和RunLoop是一一对应的。自己创建的线程默认是没有RunLoop的。
- 怎样开启一个常驻线程?
1.为当前线程开启一个RunLoop,2.向该RunLoop中添加port、source来维持RunLoop的事件循环,3.启动RunLoop。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。其实,其他线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
六、源码加深理解
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}