流程
SetupThisRunLoopRunTimeOutTimer();
do {
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks();
__CFRunLoopDoSource0(); // 处理source0事件,UIEvent事件,比如触屏点击
CheckIfExitMessagesInMainDispatchQueue(); // 检查是否有分配到主队列中的任务
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
var wakeUpPort = SleepAndWaitForWakingUpPorts(); // 开始休眠,等待ma ch_msg事件
// mach_msg_trap
// ZZz..... sleep
// Received mach_msg, wake up
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting); // 被事件唤醒
// Handle msgs
if (wakeUpPort == timePort) { // 被唤醒的事件是timer
__CFRunLoopDoTimers();
} else if (wakePort == mainDispatchQueuePort) { // 主队列有调度任务
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
} else { // source1事件,UI刷新,动画显示
__CFRunLoopDoSource1();
}
__CFRunLoopDoBlocks();
} while (!stop && !timeout)
添加 Observer 可以监听到 RunLoop 的各种状态
kCFRunLoopEntry: 进入 RunLoop 循环
kCFRunLoopBeforeTimers: 处理 定时器(Timers)
kCFRunLoopBeforeSources: 处理 Sources
kCFRunLoopBeforeWaiting: 休眠之前
kCFRunLoopAfterWaiting: 被唤醒
kCFRunLoopExit: 退出当前 RunLoop
其中检测卡顿的话 检测 kCFRunLoopBeforeSources 以及 kCFRunLoopAfterWaiting 这2个状态, 因为干活的都是在这2个状态之后。
当UI改变( Frame变化、 UIView/CALayer 的继承结构变化等)时,或手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理。
苹果注册了一个用来监听BeforeWaiting和Exit的Observer,在它的回调函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
当一个硬件事件(触摸/锁屏/摇晃/加速等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收, 随后由mach port 转发给需要的App进程。
苹果注册了一个 Source1 (基于 mach port 的) 来接收系统事件,通过回调函数触发Sourece0(所以UIEvent实际上是基于Source0的),调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。
响应链是从最合适的view开始传递,处理事件传递给下一个响应者,响应者链的传递方法是事件传递的反方法,如果所有响应者都不处理事件,则事件被丢弃。我们通常用响应者链来获取上几级响应者,方法是UIResponder的nextResponder方法。
事件的分发和传递。
在开发中,我们可以使用Xcode自带的Instruments工具的Core Animation来对APP运行流畅度进行监控,使用FPS这个值来衡量。这个工具我们只能知道哪个界面会有卡顿,无法知道到底是什么操作哪个函数导致的卡顿。
主线程大量的I/O操作
大量的UI绘制
主线程进行网络请求以及数据处理
离屏渲染
网上总结比较好的图
- 通知Observers 进入runloop
- 通知Observers 即将处理Timers
- 通知Observers 即将处理Sources
- 处理blocks
- 处理Sources0(可能会再次处理Blocks)
- 如果存在Sources1 就跳到第8步
- 通知Observers:开始休眠,等待唤醒
- 通知Observers:结束休眠 被某个消息唤醒
>1 处理Timer
>2 处理GCD AsycToMainQueue
>3 处理Source1 - 处理blocks
- 根据前面的执行结果,决定如何操作
>1 回到第二步
>2 退出Loop
1> source0 用户事件 执行PerforSelector方法,假如你在主线程performSelectors一个任务到子线程,这时候就是在代码中发送事件到子线程的runloop,这时候如果子线程开启了runloop就会执行该任务,注意该performSelector方法只有在你子线程开启runloop才能执行
2> source1 苹果创建用来接受系统发出事件,当手机发生一个触摸,摇晃或锁屏等系统,这时候系统会发送一个事件到app进程(进程通信),这也就是为什么叫基于port传递source1的原因,port就是进程端口嘛,该事件可以激活进程里线程的runloop
事件响应
当一个硬件事件(触摸/锁屏/摇晃/加速等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收, 随后由mach port 转发给需要的App进程。
苹果注册了一个 Source1 (基于 mach port 的) 来接收系统事件,通过回调函数触发Sourece0(所以UIEvent实际上是基于Source0的),调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
Autoreleasepool 跟 Runloop的关联
添加autoreleasepool 在什么状态
每个线程对应一个AutoReleasePoolPage 以 stack为节点的 双向链表
void *pool = AutoReleasePoolPage::push();
//some code
AutoReleasePoolPage::pop(pool);
如果对象直接被autoreleasepool包住,那在autoreleasepool大括号结束的时候就release;
如果对象不是被autoreleaspool包住,释放是由runloop控制的。在所属的runloop循环中,runloop休眠之前调用release
- next 指向下一个能存放autorelease对象的地址
objc_autoreleasePoolPush方法调用时候,会将一个POOL_BOUNDARY入栈(到AutoreleasePoolPage中),并且返回其存放的内存地址(即下面的atautoreleasepoolobj)
调用pop方法时传入一个POOL_BOUNDARY的内存地址(即下面的atautoreleasepoolobj),会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
autoreleasepoolObj = objc_autoreleasePoolPush()
objc_autoreleasePoolPop(atautoreleasepoolobj)
有多个autoreleasepool嵌套,如果一个poolPage还有存储空间,多个autoreleasepool的对象也会放到同一个poolPage中
Page(hot):当前使用的page;Page(cold):不是当前使用的page
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()