为了线程能够一直处理事件但并不退出而创建的模型
此类模型共同关键点:
1.如何管理事件/消息
2.在无事件时休眠,在事件来了之后立刻被唤醒
3.quit 可以停止循环
- 创建和销毁
在线程创建后第一次主动获取的时候创建
在线程结束的时候销毁 - 使用
CFRunLoopMode 和 CFRunLoop 的结构大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
主线程的 RunLoop 有两个预置 Model:
NSDefaultRunLoopMode
(默认的)
UITrackingRunLoopMode
(用于界面跟踪)
创建一个 Timer 做为一个事件添加到 RunLoop 中, RunLoop 会把这个事件放入_commonModelItems
中,当事件被触发的时候把事件同步到被标记为“Common ” 的所有 Model 。
当滑动 Scrollview 的时候,会自动切换到 UITrackingRunLoopMode
,如果这时候只是把 Timer 添加到 NSDefaultRunLoopMode
,Model 就会停止接收 Timer 事件.
可以选择添加到 NSRunLoopCommonModes
在任何 Model 下都可以接收事件。
RunLoop 的状态
entry
beforetimers
beforesources
beforewaiting
afterwaiting
exitRunLoop 的特点
启动之前必须要有一个事件在里面
如果runloop的mode中没有任何一个事件,那么runloop就会马上退出,不继续循环
自己激活的runloop可以设置超时时间来进行runloop的停止,然后使线程退出。
苹果用 RunLoop 实现的功能
AutoreleasePool
在Entry(即将进入 loop)的时候创建 AutoreleasePool,在其他所有回调之前。
在 BeforeWaiting (准备进入休眠)的时候释放旧池创建新池。
在 Exit(即将退出 loop)释放自动释放池。
监听硬件事件(触摸/锁屏/摇晃)的发生,包装成 UIEvent 进行分发
进行手势识别,执行
GestureRecognizer
的回调
界面更新
当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
定时器
NSTimer
RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。
CADisplayLink
与屏幕刷新率一致的定时器,具有更高的精密性,但如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去,就造成界面卡顿的感觉。
PerformSelecter
当调用 NSObject 的 performSelecter:afterDelay 后,会创建一个 NSTimer 加入当前线程的 RunLoop。如果当前线程没有RunLoop ,此方法会失效。
RunLoop 的实际应用举例
AsyncDisplayKit
AsyncDisplayKit 是 Facebook 推出的用于界面流畅的框架,原理如下:
UI 界面一旦出现繁重任务就会有卡顿,这些任务可能是:排版,绘制,UI操作。
ASDK 做的事情就是把能放在后台线程的放在后台,比如前两种任务,把不能的尽量推迟,比如第三种任务。
为此,ASDK 创建了一个名为 ASDisplayNode 的对象,仿照QuartzCore/UIKit框架的模式,实现了在主线程添加一个 observer,监听在 waiting 或 exit 的时候再执行早前放入的任务。
runloop的应用
Update Cycle
update cycle是应用完成了你所有的事件处理的代码后回到主runloop 时的那个时间点。正是在这个时间点上系统开始更新布局、显式、设置约束。ios 应用一般以60fps的速度展示动画,就是说每个更新周期只需要1/60秒。理解runloop、update cycle、UIView中的具体方法可以帮助你更好的进行事件处理和视图显示。
布局
layoutsubviews()
处理视图和子视图的重新定位和大小调整,会在每个子视图上调用layoutsubviews方法,不应该在代码中显式调用setneedslayout()
调用会立刻执行并返回,但在返回前不会真正调用layoutsubviews
方法更新视图。视图会在下一个 update cycle中进行更新。这个调用是最省资源的方法layoutifneeded()
是另一个会让UIView 触发layoutsubviews
调用的方法?;崛?code>layoutsubviews方法立即就调用。
可以用在需要通过改变constraint来进行动画的时候。在animation block 之前调用一次layoutifneeded,确保所有的更新已完成。然后在block里面改变 constraint,再调用layoutifneeded。
显示
drawRect
对视图内容显示的操作,只会作用于当前的view,不会触发子视图的调用。setneedsdisplay()
类似于setneedslayout,会在下次update cycle调用draw方法。
一般在需要绘制的属性的set方法里面调用此方法。
视图的显示方法里没有类似布局中的layoutifneeded这样的立即触发的方法。通常情况下等到下一个更新周期在绘制视图也没有关系
约束
apple 在ios6引入了自动布局,它允许开发者在界面上的任意两个视图之间建立精确的线性变化规则,就是:y = m*x + c;
updateConstraints()
这个方法用来在自动布局中动态改变视图约束。和布局中的layoutsubviews 和显示中的 drawrect 方法类似,不要在代码中显式调用。setneedsupdateconstraints()
保证在下一次更新周期中更新约束。通过标记“update constraints”来调用updateConstraints
。这个方法和setneedsdisplay
和setneedslayout
的工作机制类似。updateconstraintsIfNeeded()
对于使用自动布局的视图来说,这个方法与layoutifneeded等价。invalidateInstrinsicContentSize()
自动布局中某些视图拥有instrinsicContentSize 属性,这是视图根据它的内容得到的自然尺寸。调用此方法表示 instrnsicContentSize 需要在下一个更新周期进行更新。