概念
一个非主线程的声明周期,是从线程刚开始创建到任务结束的,但是假如我们想让一个线程一直处于待处理状态,即随时可以接受外界调用,则需要用到RunLoop。即RunLoop是一个对象,管理其需要处理的事件和消息,并提供了一个入口函数执行一个循环,使线程一直处于这个循环中,直到外界传入了结束消息。
与线程的关系
RunLoop 与线程是一一对应的。关系保存在一个全局字典里面,线程刚创建的时候,RunLoop 并没有创建,而是在第一次调用的时候才被创建,在线程结束的时候销毁。只能在线程内部调用它的RunLoop(主线程除外)
相关类
CoreFoundation 框架中,有5个关于 RunLoop 的类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
框架中并没有暴露 CFRunLoopModeRef 的接口,而是通过 CFRunLoopRef 进行了封装。他们之间的关系如下:
一个 RunLoop 包括多个 Mode ,而一个 Mode 包含多个 Source 、Observer 、Timer 。一个 RunLoop 只能以一个 Mode 运行,切换 Mode 的话,必须退出 RunLoop ,再重新指定一个 Mode 进入。
CFRunLoopSourceRef
Source 是产生事件的地方,有两个版本。
1.Source 0 ,只包含一个函数指针,不能自动调用这个指针,而是手动调用 CFunLoopSourceSignal(source) 将这个 source 标记为待处理状态,然后调用 CFRunLoopWakeUp(runloop) 将 runloop 唤醒,让其处理这个 source
2.Source 1 ,包含一个 mach_port 和一个回调(函数指针),port 即接口,用于内核和其他线程互相发送消息,后面会详细讲解他的用法。
CFRunLoopTimerRef
基于时间的触发器,包含一个时间长度和一个回调(函数指针),当时间点到达时,RunLoop 自动调用回调函数。
CFRunLoopObserverRef
观察者对象,可以观察 RunLoop 的各个状态,即每个观察者都有一个回调指针,当 RunLoop 状态发生变化时,观察者被调用。RunLoop 包括以下几种状态:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
CFRunLoopModeRef
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
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
RunLoopMode 与 RunLoop 的结构大致如上。
RunLoop 结构体中,有个 _commonModes 概念,Mode 可以把自己设置为 Common 状态(通过将自己的 ModeName 加入到 _commonModes 中,这样每次 RunLoop 的内容发生变化时, RunLoop 都会讲 _commonModeItems 中的 item 加入到 _commonModes 中的每个 Mode 中。
RunLoop 实现的功能
AutoReleasePool
自动释放池,假如将一段代码加入到自动释放池,那么里面使用到的对象,究竟是在什么时候被释放掉呢。
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
手势响应
当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
界面更新
当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
这个函数内部的调用栈大概是这样的:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
定时器
使用了 timer 的方式实现