RunLoop是什么(概念)
Runloop是通过内部维护的事件循环来对事件/消息进行管理的一个对象
- 事件循环(状态切换):用户态与内核态的切换。
- 事件/消息进行管理:通过mach_msg()函数接收、发送消息来进行管理。
RunLoop的数据结构
NSRunloop是CFRunloop的封装
CFRunLoop
- pthread(与线程一一对应)
- currentMode(当前的运行模式)
- modes(集合NSMutableSet<CFRunLoopMode*>)
- commonModes(一个存储了被标记为common modes的模式集合)
- commonModeItems(它是一个集合,集合里面有多个元素,有多个Observer,有多个Timer,有多个Sources)
pthread
RunLoop和线程一一对应,主线程默认开启,子线程需手动开启
currentMode
RunLoop当前所处的模式,是CFRunLoopMode数据结构
modes
- 一个Runloop有一个modes
- 一个modes有多个CFRunLoopMode组成
- 一个CFRunLoopMode是由name/Source/Timer/Observer组成
- RunLoop只能运行一个Mode,RunLoop只会处理当前Mode的事件
source--CFRunloopSourceRef
- source0:只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunloopWakeUp(runloop)来唤醒Runloop,让其处理这个事件。
- source1:基于mach_port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的Runloop,mach_port就理解线程间相互发送消息的一种机制 例如:一个App在前台静止着,此时,用户用手指点击了一下App界面,那么过程如下;
我们触摸屏幕,先摸到硬件,屏幕表面的事件会包装成Event,Event告诉source(mach_port),source1唤醒Runloop,然后将事件Event分发给source0,然后由source0来处理。 如果没有事件,也没有timer,则runloop就会睡眠,如果有,则runloop就会被唤醒,然后跑一圈。
Observer--CFRunloopObserver
观察者可观测的时间点
- kCFRunloopEntry(runloop准备启动)
- kCFRunloopBeforeTimers(通知观察者,runloop将要对Timer的一些相关事件进行处理了)
- kCFRunloopBeforeSources(将要处理一些Sources事件)
- kCFRunloopBeforeWating(即将要发生用户态到内核态的切换 用户态->内核态)
- kCFRunloopAfterWating(内核态->用户态)
- kCFRunloopExit(runloop退出通知) 这些可观察的时间点有时也可以作为检测app卡顿的功能
Timer--CFRunloopTimer
commonModes
- kCFRunLoopDefaultMode
- UITrackingRunLoopMode
- kCFRunLoopCommonModes:是一个集合,里面存储着kCFRunLoopDefaultMode、UITrackingRunLoopMode
事件循环的时间机制
- main函数->UIApplicationMain
- 在UIApplicationMain中启动主线程的Runloop
- 即将进入Runloop(通知observer)
- 将要处理timer、source0事件(通知observer)
- 处理source0事件
- 如果有source1事件要处理(则跳转到10)
- 线程将要休眠(通知observer)
- 休眠、等待唤醒(唤醒的方法:source1事件、Timer事件、外部手动唤醒)
- 线程刚被唤醒(通知observer)
- 处理唤醒时收到的消息
- 即将退出Runloop(通知observer)
mode是如何切换的
- CFRunLoopRunSpecific是启动Runloop和指定Runloop在哪个mode下执行的mode,这个函数一般由操作系统进行mode的切换
- 每一个mode处理完成后,如果runloop没有退出,就会返回之前的mode,初始mode是default
- CFRunLoopRunSpecific会保持前一次mode的状态属性(stoped和currentmode)然后发出即将要进入新的mode通知,然后进入__CFRunloopRun(会创建一个循环),然后这个mode运行结束后再发已退出mode通知。再恢复前一次的 stopped 和 currentmode
commonMode的作用
[runloop addTimer:timer foeMode:xxx]; 如果不使用这个方法,timer会默认添加kCFRunLoopDefaultMode 如果是一个TableView,则需要添加NSRunnLoopCommons,其实就是打了一个common标记的事件,正常的时候commonModes只有UITrackingRunLoopMode和kCFRunLoopDefaultMode,所以在这两个mode下,都会去执行timer事件。
AutoreleasePool
- App启动后,苹果在主线程Runloop里注册了两个Observer,其回调都是
_wrapRunLoopWithAutoreleasePoolHandler() - 第一个Observer监视的事件是Entry,其回调内会调用
_objc_autoreleasePoolPush()创建自动释放池,优先级最高,保证创建释放池发生在其他所有回调之前 - 第二个Observer监视了两个事件:BeforeWating(准备进入休眠)时调用
_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop()来释放自动释放池。优先级最低,保证其释放池子发生在其他所有回调之后。
事件响应
手势识别
界面更新
当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数: _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面
按钮点击
使用Runloop优化UITableView
有个需求,就是要从网络上加载高清大图到UITableViewCell上,而且每个Cell上面加载多张图片,当Cell量过多的时候,我们需要保持流畅度和加载速度。
- UI渲染用到了Runloop,可以监听到runloop的每次循环,在每一次循环当中我们考虑去进行一次图片下载和布局
- 既然要在每次循环执行一次任务,我们可以先把所有图片加载的任务代码块添加到一个数组当中,每次循环取出第一个任务进行执行
- 因为runloop在闲置的时候会自动休眠,所以我们要想办法让runloop始终处于循环中的状态