一、Runloop概念
Runloop是通过内部维护的事件循环(Event loop)来对事件/消息进行管理的一个对象。
没有消息处理时,休眠以避免资源的占用,由用户态切换到内核态
有消息需要处理时,立刻被唤醒,由内核态切换到用户态
二、Runloop的数据结构
NSRunLoop(Foundation)是CFRunloop(CoreFoundation)的封装,提供了面向对象的API。
主要涉及五个类:
CFRunloop : Runloop 对象
CFRunLoopMode : 运行模式
CFRunLoopSource :输入源/事件源
CFRunLoopTimer :定时源
CFRunLoopObserver:观察者
1.CFRunLoop
由pthread、currentMode、modes、commonModes、commonModeItems构成。
2.CFRunLoopMode
由name、source0,source1,observers、timers构成。
总共有5种
- kCFRunLoopDefaultMode:默认模式,主线程时在这个运行模式下运行
- UITrackingRunLoopMode:跟踪用户交互事件
- UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
- kCFRunLoopCommonModes:伪模式,是同步Source/Timer/Observer到多个Mode的一种解决方案
3.CFRunLoopSource
分为source0和source1两种
- source0
即非基于port的,也就是用户触发的事件,需要手动唤醒线程,将当前的线程从内核态切换到用户态。如:UIEvent事件。 - source1
基于port的,包含了一个mach_port 和一个回调,可以监听系统端口和通过内核接受其他线程发送的消息,能主动唤醒RunLoop,接受分发系统事件,具备唤醒线程的能力。
4.CFRunLoopTimer
基于事件的触发器,基本上说就是NSTimer,在预设的时间点唤醒RunLoop执行回调。因为他是基于RunLoop的,因此不是实时的(因为RunLoop值负责分发源的消息,如果线程正在处理繁重的任务,有可能导致Timer本次延时或者少执行一次。)。
5.CFRunLoopObserver
可以监听一下事件点
- kCFRunLoopEntry
RunLoop准备启动 - kCFRunLoopBeforeTimers
RunLoop将要处理一些Timer相关事件 - kCFRunLoopBeforeSources
RunLoop将要处理一些Source事件 - kCFRunLoopBeforeWaiting
RunLoop将要进行休眠状态,即将由用户态切换到内核状态 - kCFRunLoopAfterWaiting
RunLoop被唤醒,即从内核态切换到用户态 - kCFRunLoopExit
RunLoop退出 - kCFRunAllActivities
监听所有状态
6.各数据结构之间的关系
线程和RunLoop一一对应,RunLoop和Mode以一对多,Mode和source、timer、observer也是一对多。
二、Runloop的使用场景
1.autoreleasePool在何时被释放?
App启动后,苹果在主线程RunLoop里注册了两个Observer。
其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。
第一个Observer监视的事件是Entry(即将进入Loop),其回调会调用_objc_autoreleasePoolPush()创建自动释放池,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个Observer监视了两个事件:BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop)时调用_objc_autoreleasePoolPop()来释放自动释放池,这个Observer的优先级最低,保证其释放池子的发生在其他回调之后。
2.performSelector的实现原理?
当调用NSObject的performSelector:afterDelay:后,实际上时内部创建了一个Timer并添加到当前线程的RunLoop中。所以如果线程RunLoop,则这个方法会失效。
当调用performSelector:onThread:时,实际上也是创建了一个Timer到对应的线程中,同样,如果对应线程没有RunLoop该方法也会失效。
performSelector:afterDelay:在子线程默认没开启RunLoop,所以不起作用,可以使用GCD:Dispatch_after实现,或者在添加Timer后,启动子线程的RunLoop。
3.解释一下事件响应的过程?
苹果注册了一个Source1(基于mach port的)用来接受系统事件,其回调函数为_IOHIDEventSystemClientQueueCallback().
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由IOKit.framework生成一个IOHIDEvent事件并由SpringBoard接收。随后苹果注册的那个Source1就会触发回调,并调用_UIApplicationHandleEventQueue()进行应用内部的分发。
_UIApplicationHandleEventQueue()会把IOHIDEvent处理并包装成UIEvent进行处理或者分发,其中包括识别UIGEsture/处理屏幕旋转/发送给UIWindow等。通常事件比如UIButton点击、touchesBegin/Move/End/Cancle事件都是在这个回调里完成的。
//SpringBoard其实时一个标准的应用程序,用来管理iOS的主屏幕,它时iOS程序中,事件的第一个接受者,只能接受少数的事件,比如:按键(锁屏/静音),触摸、加速、传感器等,随后使用machPort转发给需要的App进程。
4.解释一下手势识别的过程?
当上面的_UIApplicationHandleEventQueue()识别到一个手势时,首先会调用Cancel,将当前的touchBegin/Move/End系统回调打断,随后系统将对应的UIGestureRecognizer标记为待处理。
苹果注册了一个Observer监测BeforeWaiting(Loop即将进入休眠)事件,这个Observer的回调函数是_UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的GestureRecognizer,并执行GestureRecognizer的回调。
当有UIGestureRecognizer的变化时,这个回调都会进行相应的处理。
5.利用runloop解释一下页面渲染的过程?
当我们调用[UIView setNeedsDisplay]时,这时会调用但前view.layer的[view.layer setNeedsDisplay]方法。
这等于给当前layer打了一个标记,而此时并没有进行绘制工作。而是等当前的RunLoop即将休眠的时候进行绘制工作。
CALayer内部会创建一个BackingStore,用来获取图形上下文,最终CALayer都会将位图提交到BackingStore,提交给GPU,绘制结束。
//CALayer异步绘制待总结。
网友评论