1、什么是RunLoop
RunLoop 是通过系统内部管理的事件循环管理消息和事件的对象。
Q:runloop 是否等于 while(1) { do something ... } ?
A:答案是否定的
while(1)是一个忙等的状态,需要一直占用资源。runloop没有消息需要处理时进入休眠状态,消息来了,需要处理时才被唤醒。
2、不同线程对 RunLoop 的差别对待
添加如下代码,点击屏幕
此时可以看到,输出了1、3、2。
这符合预期,没有问题~
变换一下代码,把主线程切换到全局线程
Q:可以看到输出只有1、3,并没有出现
test 方法中输出的2,为什么呢?
performSelector:在当前线程的RunLoop中开启一个timer,运行循环到来时执行timer的内容。- 主线程的
RunLoop默认开启 - 子线程的
RunLoop默认不开启
A:上述代码 performSelector: 在全局线程(子线程)中执行,RunLoop 没有开启,所以没执行。
但是为什么会区别对待呢?这需要往源码里面翻。
3、RunLoop 的结构
打印一下当前的 RunLoop
可以看到
NSRunloop 其实是 CFRunLoop 的封装。
下载 CoreFoundation 源码,搜索 runloop可以看到存在一个 CFRunLoop.c 的文件,在里面可以找到结构体 __CFRunLoop 的构成。重要的组成是红框内的四项
- 一个
RunLoop对象里面包含了若干个RunLoopMode RunLoop内部通过集合容器_modes装载这些RunLoopMode
3-1、RunLoopMode
RunLoopMode 的核心内容是4个数组容器,分别用来装 source0,source1,observer,timer。
3-1-1、source0
source0 里面存放的是一个个结构体 __CFRunLoopSource
source0:触摸事件处理、performSelector:onThread:
在 touchesBegan:withEvent:断点,使用bt命令输出函数调用栈信息
可以看到
Runloop 正在处理的触摸事件是一个 source0
同理~
3-1-2、source1
source1 里面存放的是一个个结构体 __CFRunLoopSource,负责的是系统事件捕捉、线程间通信
点击屏幕会产生一个系统事件,通过 source1 捕捉后由 Springboard 程序包装成 source0 分发给应用处理,因此我们在 App 内部接收到的触摸事件都是 source0。
线程间通信也是由 source0 处理的。
3-1-2、timers
timer 里面的是 CFRunLoopTimerRef,包括了定时器事件、[performSelector: withObject: afterDelay:]
同样的断点和
bt 命令,清晰明了~
3-1-3、observers
observers 里面是监听者,Runloop 状态变更会通知监听者进行函数回调
Runloop 有以下状态
例如监听到
Runloop 状态为 BeforeWaiting 就会刷新UI界面
3-2、_currentMode
RunLoop 对象内部的 _currentMode 指向了该 RunLoop 的其中一个RunLoopMode,就是当前运行中的 RunLoopMode。
RunLoop 当前只会执行 _currentMode 包含的事件(source0、source1、observer、timer)。
3-3、_commonModes
这么看起来只需要 modes 就满足需求了,为什么还需要 _commonModes 和 _commonModeItems 呢?
看一个常见实例:启动一个定时器,每秒执行 test 方法输出,在视图上添加一个可滚动的滚动视图。
不滑动视图时输出正常,但是当我们滑动视图时输出停止了。
定时器 NSTimer 每经过设置的间隔,往当前线程的 RunLoop 的其中一个 Mode 添加timer 事件并存入 timers数组。处理 timer事件 的时候就调用 timer 绑定的 func 或者 block。
RunLoop 内部有多个 RunLoopMode,其中两个需要重点关注
NSDefaultRunLoopMode:默认ModeNSEventTrackingRunLoopMode:界面追踪Mode
触摸事件会放到 NSEventTrackingRunLoopMode 的事件容器里,所以当用户操作屏幕界面时,App 会切换到该 NSEventTrackingRunLoopMode 下运行,处理触摸事件。
通过 scheduledTimerWithTimeInterval 增加的 timer事件 默认放到了 NSDefaultRunLoopMode,不进行触摸操作时 RunLoop 在运行这个它,所以正常处理添加的timer事件。
滑动的时候 RunLoop 就会处理 NSEventTrackingRunLoopMode 的事件,就处理不了 NSDefaultRunLoopMode 里面的 timer事件 了。
如果需要滑动的过程中同时处理定时器事件,就需要把 timer 添加到 NSRunLoopCommonModes
[[NSRunLoop currentRunLoop] addTimer:timer forMode:kCFRunLoopDefaultMode];
-
NSRunLoopCommonModes模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合 -
NSRunLoopCommonModes不是一个具体的模式,它可以理解成一个标签。 -
打上标签的
RunLoopMode会被放入到RunLoop内部的_commonModes。
此时 timer 会被放入 RunLoop 的 _commonModeItems 里。只要运行到 _commonModes 所包含的某个 RunLoopMode ,就会去处理 _commonModeItems里面的事件,RunLoopMode 本身的事件也会处理。
4、RunLoop 的运行流程
通过断点找到入口函数
4-1、CFRunLoopRunSpecific
原函数太烦,抽取重要部分看伪代码吧~
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
//通知observer 状态切换到 kCFRunLoopEntry
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//启动runloop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知observer 状态切换到 kCFRunLoopExit
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
4-2、__CFRunLoopRun
原函数太烦,抽取重要部分看伪代码吧~
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//退出循环的标签
int32_t retVal = 0;
//runloop的核心是do-while循环
do {
// 2、通知observer 状态切换到 kCFRunLoopBeforeTimers 即将处理Timer
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3、通知observer 状态切换到 kCFRunLoopBeforeSources 即将处理Source0
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
//4、处理source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 需要的话处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//5、判断存在/处理source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//存在source1,跳转到标签handle_msg处
goto handle_msg;
}
//6、通知observer 状态切换到 kCFRunLoopBeforeWaiting 即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//7、开始休眠
__CFRunLoopSetSleeping(rl);
// 等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 线程唤醒
__CFRunLoopUnsetSleeping(rl);
// 结束休眠 通知observer 状态切换到 kCFRunLoopAfterWaiting
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//处理唤醒事件
handle_msg:;
if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
// 被timer唤醒 处理timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
} else if (livePort == dispatchPort) {
// 被GCD唤醒 处理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
// 被source1唤醒 处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 设置返回值retVal
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入时传参:处理完事件立刻返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
/// 超时
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
/// 被外部调用者强制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
/// 自动停止了
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
/// source / timer / observer 都处理完了
retVal = kCFRunLoopRunFinished;
}
/// 没超时,还有mode需要处理,loop没被停止,继续循环。
} while (0 == retVal);
return retVal;
}
4-3 流程图
总结代码为流程图如下: