RunLoop的作用
- 使程序一直运行并接受用户输入
- 决定程序在何时应处理哪些Event
- 解耦主调方(发起调用)与被调方(执行线程),避免主调方被被调方阻塞。
- 节省CPU资源(闲置时就不需要CPU分配资源)
OC中的RunLoop
OC中,提供了两个RunLoop的对象:NSRunLoop 和 CFRunLoopRef。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
Swift 开源后,苹果又维护了一个跨平台 CoreFoundation 版本,这个版本的源码和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。
RunLoop 与线程的关系
- CFRunLoop 是基于 pthread 来管理的。
- 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里(但并不是说一个线程里只能有一个RunLoop,因为RunLoop中是可以嵌套RunLoop的)。
- 线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。
- RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
- 你只能在一个线程的内部获取其 RunLoop(主线程除外)。
RunLoop与NSTimer
NSTimer实例需要在RunLoop上进行调度才能正常运行,NSTimer在未加入到RunLoop前,并不会产生效果,只有当其加入到RunLoop时(个人猜测此时会将要执行的事件、对应时间点等桥接到RunLoop的对应属性中),才会在RunLoop的循环中被执行。
更多NSTimer相关参考:
NSTimer详解----使用、保留环问题、与runloop的关系
重新认识NSTimer以及他与RunLoop关系
RunLoop与其他Cocoa相关
-
UIEvent事件的回调是依托在主线程的RunLoop中,基本上所有UI事件的起点都在MainRunLoop中,这也是为什么如果在主线程中有大量耗时处理时会造成App反应迟钝。
-
Autorelease,自动释放池中的变量会在其RunLoop的一次循环即将结束时才将池中的对象释放。
-
调用PerformSelecter方法时,会在内部创建一个Timer并添加到对应RunLoop中。
-
CADisplayLink、CATransition、CAAnimation等等与界面显示相关的方法,都与主线程的RunLoop执行频率有关,RunLoop在一个循环结束前收集所有界面变化,汇总后在循环结束时调用渲染的方法进行渲染。
-
RunLoop的实现中使用了许多GCD相关代码来进行线程的调度工作,RunLoop与GCD只是协作关系,并不是对GCD的封装。
-
NSURLConnection进行网络请求时,不论是同步还是异步,都需要用到RunLoop来等待返回值。
-
等等...
RunLoop的流程
下图就是一个RunLoop的流程:

关于source0和source1
- source0负责App内部事件,由App负责管理触发,例如UIEvent、UITouch事件,一般来说,App内部的调用都属于它的范围。
- source1 基于port的:包含一个 mach_port 和一个回调,可监听系统端口和通过内核和其他线程发送的消息,能主动唤醒runloop,一般用于接收分发系统事件和App间的相互通信,一些硬件事件(触摸/锁屏/摇晃等)也是用source1来进行接收,然后调用_UIApplicationHandleEventQueue() 进行应用内部的分发(这就变成了source0)。
什么时候使用RunLoop
- 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)
- 在子线程中开启一个定时器
- 在子线程中进行一些长期监控
- 控制定时器在特定模式下执行
- 让某些事件(行为、任务)在特定模式下执行
- 添加Observer监听RunLoop的状态,比如监听点击事件的处理(在所有点击事件之前做一些事情)
参考文章:
关于Runloop的原理探究及基本使用
深入理解RunLoop
解密——神秘的RunLoop
NSRunLoop原理详解——不再有盲点
网友评论