1、Runloop是什么?
Runloop作为OC底层的一个重要组成部分,保证了我们的代码不会在执行完毕后就退出程序,Runloop的本身也是作为一个对象存在的。
Runloop的运行本质就是一个do...while循环存在于底层。如下图所示
2、Runloop和线程的关系?
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
线程和Runloop是一一对应的的,关系保存在一个全局的字典中,线程创建时Runloop时是没有的,如果我们不主动获取,一直不会存在,当我们进行获取时,runloop会进行创建,runloop会在线程任务结束后销毁。(所以子线程的runloop默认不开启)
3、Runloop的结构 Runloop底层包括了CFRunLoopRef、CFRunLoopModeRef CFRunLoopSourceRef、CFRunLoopTimerRef、CFRunLoopObserverRef 五个类, 一个 RunLoop可以包函多个Model,一个model又可以包函多个Source/Timer/Observer,Runloop在运行时,一次只能运作在一个model,如果需要切换model,需要退出runloop才能重新指定一个model。
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
...
};
Source分为_sources0和_sources1,_sources0不会主动触发(用户触发事件,需要手动唤起线程),需要将_sources0标记为待触发,当runloop运行时,才会去处理这个事件( 如果runloop处于休眠中,则不会唤醒处理)。_sources1是基于port的,包函一个mach_port和一个回调,可以监控系统端口和通过内核与其他线程发送的消息,能主动唤醒runloop,接收分发系统事件,具有唤醒线程的能力。
Timters基于时间的触发器,在预设的时间点唤醒回调。因为它是基于runloop的(这就是定时器不准确的原因,因为runloop只负责分发消息,如果线程当前处于忙碌状态,就可能导致定时器延时。)
observer监听包函一个回调,用于监听以下的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
};
4、Runloop的作用。
iOS中定时器、事件的响应、手势的识别以及页面的渲染都和runloop有关。
定时器:其实就是CFRunLoopTimerRef,在一个定时器注册到runloop后,Runloop会在重复的时间点注册好事件,runloop会在对应的时间点去回调timer,但是为了节省资源,不会在非常准确的点回调,timer本身有一个Tolerance(宽容度)属性,容许有多少最大误差。如果当前的回调时间点被错过,就会跳过去,不会延后执行。
事件的响应:系统注册了一个source1 用于接收系统事件,当一个硬件事件(如:触摸,锁屏,摇晃等)发生后,首先由IOKit生成一个IOHIDEvent事件并由SpringBoard接收,然后用mach_port转发给app进程,随后会触发source1的回调, 并调用_UIApplicationHandleEventQueue() 进行应用内部的分发。UIApplicationHandleEventQueue()会将IOHIDEvent处理包装为UIEvent进行处理和分发。包括识别手势,处理屏幕旋转等,常见的按钮点击事件,touches事件都是在这个回调处理。
手势的识别:UIApplicationHandleEventQueue() 识别到一个手势后,会调用cancle将touchesBegin/Move/End系列回调打断,随后系统将对应的手势标记为待处理,系统注册了一个observer监测runloop即将休眠事件,这个observer的回调_UIGestureRecognizerUpdateObserver,内部会获取刚被标记为待处理的手势,并执行回调。当有手势的变换时,这个回调都会进行相应的处理
页面的渲染:当我们调用 [UIView setNeedsDisplay] 时,这时会调用当前 View.layer 的 [view.layer setNeedsDisplay] 方法。 这等于给当前的 layer 打上了一个标记,而此时并没有直接进行绘制工作。而是会 到当前的 Runloop即将休眠,也就是 beforeWaiting 时才会进行绘制工作。 紧接着会调用 [CALayer display] ,进入到真正绘制的工作。 CALayer 层会判断自己的delegate有没有实现异步绘制的代理方法 displayer: ,这个代理方法是异步绘 制的入口,如果没有实现这个方法,那么会继续进行系统绘制的流程,然后绘制结 束。 CALayer 内部会创建一个 Backing Store ,用来获取图形上下文。接下来会判断这个 layer 是否有 delegate 。 如果有的话,会调用 [layer.delegate drawLayer:inContext:] ,并且会返回给我们 [UIView DrawRect:] 的回调,让我们在系统绘制的基础之上再做一些事情。 如果没有 delegate ,那么会调用 [CALayer drawInContext:] 。 以上两个分支,最终 CALayer 都会将位图提交到 Backing Store ,最后提交给 GPU 。 至此绘制的过程结束。
5、Runloop的基本运行流程。
(1)通知观察者Runloop即将启动。 (2)通知观察者即将处理timer事件。 (3)通知观察者即将处理source0事件。 (4)处理source0事件。 (5)如果source1事件存在并处于等待状态进入步骤(9)。 (6)通知观察者线程即将进入休眠状态。 (7)将线程休眠,直到有下面任一事件唤醒线程(一个基于 port 的 Source1 的事件。 一个 Timer 到时间了。RunLoop 自身的超时时间到了。 被其他调用者手动唤醒。) (8)通知观察者线程将被唤醒。 (9)处理唤醒时收到的事件。 (10)通知观察则runloop结束。
自动释放池子什么时候被释放? APP启动后,苹果在主线程的runloop注册了两个Observer其回调都是_wrapRunLoopWithAutoreleasePoolHandler() 。第一个监听即将进入runloop的事件,其回调回调用_objc_autoreleasePoolPush() 创建自动释放池。第二个监听两个事件,一个是即将休眠,调用_objc_autoreleasePoolPop() 和_objc_autoreleasePoolPush() 释放旧的池并创建新池;一个是退出是调用_objc_autoreleasePoolPop来释放自动释放次。