RunLoop
就像尼斯湖水怪,只是听说过从来没见过.今天我们就来解开它神秘的面纱.
RunLoop
顾名思义:运行循环.他是维持我们一个App能够运行的关键.比如我们创建的iOS
项目,在main
函数中系统就会自动帮我们创建runloop
对象:return UIApplicationMain(argc, argv, nil, appDelegateClassName);
.
RunLoop
的基本作用就是:
- 保证程序的基本运行.
- 处理App中的各种事件 (比如:触摸事件,定时器事件 等等).
- 节省 CPU 资源,提高程序性能: 该做事时做事,没有事的时候就休息.
RunLoop
工作原理的伪代码大概如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
int retVal = 0;
do {
//睡眠中等待消息
int message = sleep_and_wait();
//处理消息
retVal = process_message(message);
} while (retVal = 0);
return 0;
}
}
流程:条件成立的时候一直循环:有事情就处理事情,没有事情就休眠睡觉.
iOS
中提供了两套API
来访问RunLoop
:
-
Foundation : NSRunLoop
: OC 框架 -
Core Foundation : CFRunLoopRef
: C 语言框架
NSRunLoop
是对CFRunLoopRef
的一层封装.CFRunLoopRef
源码的下载地址.我们下载好源代码后新建一个项目,把源代码拖到项目中,找到CFRunLoop.c
文件 -> 然后找到CFRunLoopGetCurrent
函数 -> 进入_CFRunLoopGet0
函数,会发现下面几句代码:
static CFMutableDictionaryRef __CFRunLoops = NULL; //字典
// 获取 runloop 对象 参数:传入一个 字典 和 key (线程)
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
//如果 runloop 不存在 , 就创建,并放到字典中
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&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
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
从上面可以看出:
RunLoop是在第一次获取的时候创建的,并且
RunLoop和 线程 是 一一对应的关系,
RunLoop是存放在一个全局字典中:以线程作为
key,
RunLoop作为
value.
RunLoop 底层结构:

RunLoop 的状态:
RunLoop
有以下几种状态:
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 // 以上所有状态
};
下面我们写代码来验证一下这些状态的切换.
首先写代码测试一下,NStimer
唤醒RunLoop
:
//监听方法
void observerRunLoop(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
switch (activity) {
case kCFRunLoopEntry:
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@",mode);
break;
}
case kCFRunLoopBeforeTimers:
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeTimers - %@",mode);
break;
}
case kCFRunLoopBeforeSources:
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeSources - %@",mode);
break;
}
case kCFRunLoopBeforeWaiting:
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeWaiting - %@",mode);
break;
}
case kCFRunLoopAfterWaiting:
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopAfterWaiting - %@",mode);
break;
}
case kCFRunLoopExit:
{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@",mode);
break;
}
default:
break;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
//创建 observer 方法二:
// CFRunLoopObserverRef observer2 = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// // 监听触发的操作
// });
// 创建 observer 方法一:
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerRunLoop, NULL);
// 添加 observer
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放 observer
CFRelease(observer);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[NSTimer scheduledTimerWithTimeInterval:3 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"------定时器触发");
}];
}
// 控制台打印:
2019-12-07 14:59:52.485737+0800 RunLoop状态监听[9050:2703905] ------定时器触发
2019-12-07 14:59:52.485925+0800 RunLoop状态监听[9050:2703905] kCFRunLoopBeforeTimers - kCFRunLoopDefaultMode
2019-12-07 14:59:52.486105+0800 RunLoop状态监听[9050:2703905] kCFRunLoopBeforeSources - kCFRunLoopDefaultMode
2019-12-07 14:59:52.486327+0800 RunLoop状态监听[9050:2703905] kCFRunLoopBeforeWaiting - kCFRunLoopDefaultMode
然后我们再创建一个UITextView
,拖动UITextView
看看RunLoop
状态的切换情况,为了更清晰的看到RunLoop
切换状态,我们把Switch
语句中其他的状态都注释掉,只保留kCFRunLoopEntry
和kCFRunLoopExit
两种状态,然后拖动TextView
:
2019-12-07 15:03:34.797449+0800 RunLoop状态监听[9079:2707284] kCFRunLoopExit - kCFRunLoopDefaultMode
2019-12-07 15:03:34.797675+0800 RunLoop状态监听[9079:2707284] kCFRunLoopEntry - UITrackingRunLoopMode
2019-12-07 15:03:35.520897+0800 RunLoop状态监听[9079:2707284] kCFRunLoopExit - UITrackingRunLoopMode
2019-12-07 15:03:35.521162+0800 RunLoop状态监听[9079:2707284] kCFRunLoopEntry - kCFRunLoopDefaultMode
2019-12-07 15:03:35.832982+0800 RunLoop状态监听[9079:2707284] kCFRunLoopExit - kCFRunLoopDefaultMode
可以看到RunLoop
频繁的在kCFRunLoopDefaultMode
和UITrackingRunLoopMode
之间切换.
刚才我们用伪代码概括了RunLoop
内部的工作流程,那RunLoop
内部到底是如何工作的呢?我们通过源码看一下.
RunLoop 源码分析:
我们找到CFRunLoop.c
源码,发现里面有很多函数,哪一个才是我们想要的RunLoop
入口函数呢?很简单,我们可以写一个- (void)touchesBegan:withEvent:
方法,然后再这个方法内部打一个断点,因为我们知道系统事件都是由RunLoop
来捕捉管理的.走到断点后,我们使用LLDB
指令bt
打印所有函数调用栈:

在
CFRunLoop
中搜索CFRunLoopRunSpecific
函数,简化后如下:
// RunLoop 入口函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
//通知 observer: 进入 loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知observer`: 退出 loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
所以我们重点研究__CFRunLoopRun
函数内部做了什么事情.
我把__CFRunLoopRun
函数精简了一部分,并且添加了注释:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
//通知observer: 即将处理 Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知observer: 即将处理 Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理 blocks
__CFRunLoopDoBlocks(rl, rlm);
//处理Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
//处理 blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//判断有无 Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//如果有 Source1 , 就直接跳转到 handle_msg
goto handle_msg;
}
didDispatchPortLastTime = false;
//通知observer: 即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//进入休眠 (睡觉)
__CFRunLoopSetSleeping(rl);
//等待别的消息来唤醒当前线程,如果唤醒就继续往下走;如果没有被唤醒,就会阻塞在这个位置等待别人唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
//唤醒 (不睡觉)
__CFRunLoopUnsetSleeping(rl);
//通知observer: 结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被 timer 唤醒) {
//处理 Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}else if (被 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);
//设置返回值,最后决定将要干什么事情
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)) {
retVal = kCFRunLoopRunFinished;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);
return retVal;
}
用下图总结一下RunLoop
内部执行的流程:

线程阻塞的细节:
我们上面讲的如果没有事情做的时候RunLoop
会进入休眠状态,会阻塞当前线程,不会继续往下执行.但是这里要搞清楚,RunLoop
的阻塞线程和我们平常代码写的阻塞线程可不一样,比如说下面这种阻塞线程:
while(1){
NSlog(@"阻塞线程");
}
像这种就是个死循环,阻塞线程的时候线程并没有休息,而是一直在判断条件并执行代码.而RunLoop
的阻塞是真正意义上的休息,什么事情也不管,一句代码都不执行,CPU不会分配任何资源.
那么RunLoop
是如何做到这样的呢?
因为API
的调用分为用户态和内核态.内核态的代码是非常底层的,不对外开放.如果RunLoop
要进入休眠状态的时候,用户态的应用代码会调用mach_msg()
函数,就会转到内核态,在内核态内部调用内核态的mach_msg ()
函数进入真正的休眠状态.
网友评论