重读 ibireme
RunLoop 的概念
- 一个线程一次只能处理一个任务,处理完线程就销毁了。我们希望线程能随时处理事件,且不退出。
function loop() {
initialize();
do {
var message = get_next_message();
processs_message(message);
} while (message != quit)
}
实现这种模型的关键是:在 message 不为 quit 时,如何管理事件/消息,让线程在没有处理消息时休眠以避免资源占用,在有消息到来时唤醒。
-
runloop 实际上就是一个对象,用来管理事件和消息,并提供了一个进入 event loop 的函数入口。一旦线程进入入口就一直处于该函数体内执行“接受消息->处理消息->等待”循环中,直到这个循环结束(比如传入 quit 消息),函数返回。
-
苹果提供了两个 event loop 这样的对象:NSRunloop,CFRunloopRef。CFRunloopRef 是面向 C 的 CoreFoundation 框架的 API,API 是线程安全的。NSRunloop 是面向 OC 的 Foundation 框架的 API,API 不是线程安全的。
RunLoop 与线程的关系
- 线程刚创建时并没有 runloop,如何你不主动获取,那它一直都不会有。
- 苹果没有提供创建 runloop 的 API,只提供了两个获取 runloop 的 API: CFRunloopGetMain(), CFRunloopGetCurrent()。
- 在线程里创建 runloop 时,会将线程作为 key,runloop 对象作为 value 保存在一个全局 loopsDic 里。
- 线程结束时,会销毁该 runloop 对象。
- 从源码里看,不管是创建一个 runloop,还是获取 runloop,线程都要作为参数。所以只能在当前线程里获取 runloop(除 main run loop 外)。
Runloop 对外接口
-
在 CoreFoundation 里关于 Runloop 有5个类:
- CFRunloopRef
- CFRunloopModeRef
- CFRunloopSourceRef
- CFRunloopObserverRef
- CFRunloopTimerRef
其中 CFRunloopModeRef 类并没有对外暴露,只是通过 CFRunloopRef 接口进行了封装。
它们的关系是:一个 runloop 可以包含多个 mode,每个 mode 又包含多个 source, observer, timer。每次运行 runloop 时只能指定一个 mode,所以各 mode 的 source 等不受影响。
-
CFRunLoopSourceRef 事件产生的地方
-
CFRunloopTimerRef 是基于时间的触发器,当 timer 加入到 runloop 时,runloop 会注册对应的时间,时间一到,唤醒 runloop 执行它的回调。
-
如果一个 mode 中一个 item 也没有,则 runloop 会直接退出,不进入循环。
runloop mode
-
苹果支持5中 mode
- NSDefaultRunLoopMode
- NSEventTrackingRunLoopMode
- NSRunLoopCommonModes
- NSConnectionReplyMode:cocoa用这个模式结合 NSConnection 对象监测回调。
- NSModalPanelRunLoopMode
-
程序启动后,系统注册了5个 mode
- kCFRunLoopDefaultMode
- UITrackingRunLoopMode
- kCFRunLoopCommonModes
- UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
- GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到。
苹果用 Runloop 实现的功能
PerformSelecter
- 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
- 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
关于网络请求
通常使用 NSURLConnection 时,我们会传入一个 delegate,并调用 [connection start]。start API 里会主动获取当前的 runloop,并在 defaultMode 里创建几个 source0,其中一个 source0 用来处理 delegate 回调。
当开始网络传输时,NSURLConnection 会创建两个线程:URLConnectionLoader 线程和 CFSocket 线程。CFSocket 线程用来处理底层 socket 连接,URLConnectionLoader 线程会在其 runloop 中监听 socket 线程 port 消息,若有消息会通知 delegate 线程注册的 source0 事件并处罚 delegate 回调。
socket 线程通过 mach port 通知 urlconnectionloader 的 runloop;urlconnectionloader 通过标记 delegate 线程的 source0 事件为待处理状态,并 wakeup delegate 线程 runloop 处理 delegate 回调。
我觉得是 urlconnectloader 持有 source0,并 add source0 到 delegate runloop default mode 里。
Mach Port
参考
所有的进程间通信都最终依赖于 Mach 内核 API 提供的功能。
Mach Port 就是一个轻量级的,功能强大的 API,来接受和发送消息,通过 mach_msg(port) 。
幸运地是:CoreFoundation 和 Foundation 提供了更高级别的 Mach port:CFMachPort,NSMachPort。且它们可以被用在 runloop source 上。(CFMessagePort / NSMessagePort 有利于两个端口间同步通信)