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
本身的事件也会处理。
3、RunLoop
的运行流程
通过断点找到入口函数
3-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;
}
复制代码
3-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;
}
复制代码
总结代码为流程图如下: