概述
Runloop
是一个与线程
相关的基础结构Runloop
是一个事件处理循环,用于安排工作和协调传入事件的接收Runloop
的目的是在有工作要做时让线程
保持忙碌,在没有工作时让线程
进入睡眠状态Runloop
管理不是完全自动的,在设计线程
代码的时候,必须在适当的时间启动运行循环- 在主线程中
Runloop
会自动启动,在子线程中需要手动的开启
Runloop解析
RunLoop
接受事件主要来自Input sources输入源
和Timer sources时钟源
Input sources
:主要来自其他线程或其他应用,发送异步事件Timer sources
:发送同步事件,发生在预定的时间或重复的时间间隔(occurring at a scheduled time or repeating interval
)
官网有一张图做了总结:显示runloop和各种源的概念结构
Input sources
将异步事件传递给相应的处理程序,并导致runUntilDate:方法(在线程的关联nsrunlop对象上调用)退出Timer sources
向其处理程序例程传递事件,但不会导致运行循环退出
RunLoop Mode
RunLoop Mode
:是要监视的输入源和计时器的集合,以及要通知的运行循环观察者的集合每次运行run循环时,都会指定(显式或隐式)要运行的特定“模式”
在运行循环的这一过程中,只有与该模式关联的源被监视并允许传递它们的事件
模式的类型
-
NSDefaultRunLoopMode
:默认模式,一般情况下使用这个模式 -
NSConnectionReplyMode
: 将此模式与NSConnection
对象结合使用来监视回复 -
NSModalPanelRunLoopMode
:使用这种模式来识别用于模态面板的事件 -
NSEventTrackingRunLoopMode
: 使用该Mode
跟踪来自用户交互的事件,例如:UIScrollView
上下滑动 -
NSRunLoopCommonModes
:这是一种组合模式,该组合默认包括默认、模态和事件跟踪模式
虽然官方文档中定义了5种模式,但是iOS中只暴露NSDefaultRunLoopMode
和NSRunLoopCommonModes
两种
Input Source(输入源)
输入源以异步方式向线程传递事件。事件的源取决于输入源的类型,输入源通常为两类之一。基于端口的输入源监视应用程序的Mach端口。自定义输入源监视自定义事件源。运行循环而言,输入源是基于端口的还是自定义的并不重要。系统通常实现两种类型的输入源,您可以按原样使用。这两个信号源之间的唯一区别是它们的信号方式。基于端口的源由内核自动发出信号,自定义源必须从另一个线程手动发出信号
Port-Based Sources(基于端口的source)
- 在Cocoa中,根本不需要直接创建输入源,只需创建一个端口对象,并使用
NSPort
的方法将该端口添加到运行循环中 - 官方创建的案例:Configuring a Port-Based Input Source.
Custom Input Sources(自定义输入源)
- 若要创建自定义
输入源
,必须使用与核心基础中的CFRunLoopSourceRef
不透明类型相关联的函数。 - 可以使用几个回调函数配置自定义输入源,Core Foundation在不同点调用这些函数来配置源,处理任何传入事件,并在从运行循环中移除源时拆掉源。
- 有关如何创建自定义输入源的示例,Defining a Custom Input Source
Cocoa Perform Selector Sources
- 除了基于端口的源之外,Cocoa还定义了一个自定义输入源,允许您在任何线程上执行
Selector
。 - 与基于端口的源一样,执行选择器的请求在线程上是序列化,从而减轻了在一个线程上运行多个方法时可能出现的许多同步问题
- 与基于端口的源不同,执行选择器源在执行其选择器后将自己从运行循环中移除。
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
在应用程序的主线程的下一个运行循环周期中,在该线程上执行指定的选择器。这些方法允许您在执行选择器之前阻塞当前线程。
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
在具有NSThread对象的任何线程上执行指定的选择器。这些方法允许您在执行选择器之前阻塞当前线程。
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
在下一个运行循环周期和可选延迟期之后,在当前线程上执行指定的选择器。因为它会等到下一个运行循环周期执行选择器,所以这些方法提供了一个来自当前执行代码的自动最小延迟。多个排队选择器按其排队顺序依次执行
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
取消一个通过performSelector:withObject:afterDelay:
和performSelector:withObject:afterDelay:inModes:
发送的的消息,
Timer Sources
Timer Sources
在将来的预设时间同步地向线程传递事件Timer
是线程通知自己做某事的一种方式- 虽然它生成基于时间的通知,但
Timer
不是实时机制,所以Timer
可能不准 - 与
Input Source
一样,timer与Runloop
的特定模式相关联 - 如果
timer
未处于运行循环当前监视的模式,则在timer
支持的模式之一运行runLoop
之前,它不会启动 - 类似地,如果一个计时器在
Runloop
处于执行一个处理程序的中间时触发,则timer
等到runloop
的下一次调用它的处理程序。如果Runloop
根本没有运行,则timer
永远不会触发
有关配置计时器源的详细信息,请参阅:Configuring Timer Sources,参考资料:NSTimer Class Reference和CFRunLoop Timer Reference
Run Loop Observers
- 与在适当的异步或同步事件发生时触发的源不同,
Run Loop Observers
在RunLoop
执行期间在特殊位置触发 - 可以使用
Run Loop Observers
来准备Thread
以处理给定事件,或者在Thread
进入睡眠之前准备Thread
,我们可以将Run Loop Observers
与RunLoop
中的以下事件关联:
- 进入
RunLoop
的时 - 当
RunLoop
即将处理Timer
时 - 当
Runloop
即将处理Input Source
时 - 当
RunLoop
即将进入睡眠状态时 - 当
RunLoop
唤醒时,但在它处理唤醒它的事件之前 - 当
RunLoop
退出时
要创建运行循环观察者,需要创建CFRunLoopObserverRef
不透明类型的新实例。
The Run Loop Sequence of Events
每次运行它时,线程的RunLoop
都会处理挂起的事件,并为任何连接的观察者生成通知。它这样做的顺序非常具体,如下所示:
- 通知
observer
已进入RunLoop
- 通知
observer
准备就绪的Timer
即将启动 - 通知
observer
任何非基于端口的输入源即将触发。 - 启动任何准备启动的非基于端口的输入源。
- 如果基于端口的输入源已准备就绪并等待触发,请立即处理该事件。转至步骤9
- 通知
observer
线程即将休眠 - 将线程置于睡眠状态,直到发生以下事件之一:
- 基于端口的输入源的事件到达。
- 计时器启动。
- 为运行循环设置的超时值过期。
- 运行循环被显式唤醒。
- 通知观察者线程刚刚唤醒。
- 处理挂起的事件。
- 如果触发了用户定义的计时器,请处理计时器事件并重新启动循环。转至步骤2
- 如果触发了输入源,则传递事件。
- 如果运行循环被显式唤醒但尚未超时,请重新启动循环。转至步骤2
- 通知观察者运行循环已退出
用一张图来总结上面的流程:
(什么时候使用runloop)When Would You Use a Run Loop?
- 唯一需要显式运行运行循环的时间是为应用程序创建子线程时
- 主线程的runloop,系统自动创建
- 使用端口或自定义输入源与其他线程通信
- 在线程上使用计时器。
- 在Cocoa应用程序中使用任何PerformSelect…方法
- 线程保活
总结,这篇文章主要是基于苹果官方文档的翻译,主要讲解了一些runloop
的理论概念。