Run Loop 基本概念
Run Loop
就是一个在线程(thread)里不停执行的do-while循环。当线程接收到事件(event)时,Run Loop 内的事件处理会使用对应的句柄(handler)处理事件。
Run Loop 接受的事件可分为两种不同的源(source),Input source
传递异步事件,通常是其他线程或应用发送过来的消息(message)。Timer sources
传递同步事件,即发生于特定时间的或以一定时间间隔循环发送的事件。
上图展示了 Run Loop 的工作原理:Run Loop 运行与线程之中,从Input source
和Timer sources
接受事件,然后调用相应的 handler 处理事件。iOS框架 Foundation 中定义了 Run Loop 的实现类NSRunLoop
。
Run Loop 与线程的关系
Run Loop 与线程是一一对应的关系。每一个线程都有且仅有一个 Run Loop 与其对应,没有线程,就没有 Run Loop。在iOS应用中,主线程的 Run Loop 是默认启动的,而其他线程的 Run Loop 默认是不启动的。苹果为我们提供了两种获取 Run Loop 对象的方式:
-
使用
[NSRunLoop currentRunLoop]
获取NSRunLoop
对象
获取的 Run Loop 对象的线程安全性取决于你所使用的API。Core Foundation 中的函数通常是线程安全的,可以从任何线程调用。但是,如果你正在执行修改 Run Loop 配置的操作,那么最佳实践是尽可能在 Run Loop 所在的线程进行这些操作。
NSRunLoop
类不具有线程安装性。如果你使用NSRunLoop
类来修改 Run Loop,则应仅从持有该 Run Loop 的线程内执行操作。
Run Loop 的组成部分
一个 Run Loop 包含多个 Mode,每个 Mode 包含多个 Sources、Objservers 和 Timers。每次调用 Run Loop 时,需要指定一种 Mode,此时 Run Loop 只能处理该 Mode 包含的Sources、Objservers 和 Timers[1]。
苹果官方文档中提到的 Mode 类型有五种:
iOS中可供调用的只有NSDefaultRunLoopMode
和NSRunLoopCommonModes
两个,其中NSRunLoopCommonModes
是一个集合,其中默认包括NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode
。
何时使用 Run Loop?
苹果官方文档[2]指出,需要显式运行 Run Loop 的唯一时机是为应用程序创建辅助线程(secondary thread)时。例如,如果你需要执行以下任何操作,则需要启动 Run Loop:
-
使用端口(mach port)或自定义输入源(custom input source)与其他线程通信。
-
在线程中使用计时器(timers)。
-
使用任何
performSelector
方法。 -
保持线程以执行周期任务。
其中常用的是timers和performSelector
。
Timer
计时器源(timer source)在将来的预设时间将事件同步传递给线程。苹果为我们提供了两种计时器的实现,NSTimer
和CFRunLoopTimerRef
。
RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。[2]
performSelector:
当调用NSObject
的performSelecter:afterDelay:
后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。当调用performSelector:onThread:
时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 Run Loop 该方法也会失效。[2]
网友评论