RunLoop简介
一般来说,一个线程只能执行一个任务,执行完就会退出,如果我们需要一种机制,让线程能随时处理时间但并不退出,那么 RunLoop 就是这样的一个机制。Runloop是事件接收和分发机制的一个实现。
RunLoop实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。
RunLoop 基本作用
- 保持程序持续运行,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,RunLoop保证主线程不会被销毁,也就保证了程序的持续运行
- 处理App中的各种事件(比如:触摸事件,定时器事件,Selector事件等)
- 节省CPU资源,提高程序性能,程序运行起来时,当什么操作都没有做的时候,RunLoop就告诉CPU,现在没有事情做,我要去休息,这时CPU就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop就会立马起来去执行对应任务。
Apple提供了一个RunLoop运行原理的示意图:


RunLoop在循环过程中,当接收到 Input sources
或者 Timer sources
时就会交给对应的处理方去处理。当没有事件消息传入的时候,RunLoop就休息了。
主线程上 RunLoop 开启
大家应该都知道iOS程序的入口当为main函数,UIApplicationMain函数内启动了Runloop,程序不会马上退出,而是保持运行状态。因此每一个应用必须要有一个runloop,
我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop,那么RunLoop一定是在程序的入口main函数中开启。
1 | int main(int argc, char * argv[]) { |
进入上面main函数返回的UIApplicationMain函数:
1 | UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName); |
我们发现它返回的是一个int类型的值,那么我们对main函数做一些修改:
1 | int main(int argc, char * argv[]) { |
运行程序,我们发现只会打印开始,并不会打印结束,这说明在UIApplicationMain
函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain
不会返回,一直在运行中,也就保证了程序的持续运行。
RunLoop 源码
RunLoop 实际上是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行 Event Loop 的逻辑。线程执行了这个函数后,函数会一直处于 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息)。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。CFRunLoopRef
是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。CFRunLoopRef代码是开源的, https://opensource.apple.com/tarballs/CF/ 可以下载最新的源码。
1 | // 用DefaultMode启动 |
我们发现RunLoop确实是do while
通过判断result的值实现的。因此,我们可以把RunLoop看成一个死循环。如果没有RunLoop,UIApplicationMain
函数执行完毕之后将直接返回,也就没有程序持续运行一说了。
获取RunLoop对象
类似获取线程的方法:1
2
3
4
5
6
7//获取 main thread 的方法:
pthread_main_thread_np() ;
[NSThread mainThread];
//获取当前线程的方法:
pthread_self();
[NSThread currentThread]
获取对应线程上的RunLoop的方法:1
2
3
4
5
6
7Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
从CoreFoundation框架中的这两个方法的源码:
1 | // 拿到当前Runloop 调用_CFRunLoopGet0 |
从上面的代码可以看出,获取RunLoop时需要传pthread_t t
作为参数,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary
里。
线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
RunLoop和线程间的关系
- 每条线程都有唯一的一个与之对应的RunLoop对象;
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value;
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建;
- RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
只能在一个线程的内部获取其 RunLoop(主线程除外)。