春天来咯iOS面试题iOS 开发每天分享优质文章

iOS学习笔记 利用RunLoop原理去监控卡顿

2019-04-11  本文已影响91人  DrunkenMouse

造成卡顿的原因:

是否监视FPS来确定卡顿? 即使帧数低,页面也可能是连贯的,所以监控FPS不合理。比如动画1秒也就24张图。

RunLoop原理

监控卡顿,就是要找到主线程干了什么。线程的消息传递依赖于NSRunLoop,所以从NSRunLoop入手,就可以知道主线程调用了哪些方法。

通过监听NSRunLoop的状态,可以发现调用方法是否执行时间过长,从而判断是否会出现卡顿。

RunLoop:监听输入源,进行调度处理。会接受两种类型的输入源:另一个线程或者另一个App的异步消息 与 来自预定时间或重复间隔的同步事件。所以输入源可以是周期性(NSTimer)、延迟时间(dispatch_after)、异步回调(async或另一个App的调用)、网络、输入设备。

RunLoop的目的:当有事件要去处理时保持线程忙,当没有事件要处理时让线程进入休眠。

RunLoop的原理主要分七步:

  1. 通知observers(监听者、观察者):RunLoop要开始进入loop了,随后进入loop。

  2. 开启一个do while 来保活线程。通知observers:RunLoop会触发Timer回调、Sourcer0回调,接着执行加入的Block。然后触发Source0回调,如果有Source1是ready状态的话,就会跳转到handle_msg去处理消息。

  3. 回调触发后,通知Observers:RunLoop的线程将进入休眠(sleep)状态。

4.进入休眠后,会等待mach_port的消息,以再次唤醒。被唤醒的四个事件:

  1. 唤醒时通知Observer:RunLoop 的线程刚刚被唤醒。

  2. RunLoop被唤醒后开始处理消息:

消息执行完后,执行加到loop里的block

  1. 根据当前RunLoop的状态判断是否走下一个loop。当被外部强制停止或loop超时,就不继续下一个loop,否则继续走下一个loop。

所以整个loop的状态包括六个:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry , // 进入 loop
    kCFRunLoopBeforeTimers , // 触发 Timer 回调
    kCFRunLoopBeforeSources , // 触发 Source0 回调
    kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
    kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
    kCFRunLoopExit , // 退出 loop
    kCFRunLoopAllActivities  // loop 所有状态改变
}

如果RunLoop的线程,进入睡眠前的执行时间过长而无法入睡。或唤醒后接收消息时间过长而无法进行下一步,就可以认为是线程受阻了。如果当期线程是主线程,则就是卡顿。

所以,要监控RunLoop在进入睡眠前或唤醒后的两个loop状态:kCFRunLoopBeforeSources 与 kCFRunLoopAfterWaiting。

要想监听RunLoop,需要创建一个RunLoop的观察者,将创建好的观察者添加到主线程的RunLoop的common模式下观察。 然后创建一个持续的子线程专门用来监控主线程的RunLoop。

一旦监听到进入睡眠前的kCFRunLoopBeforeSources 或被唤醒后的kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿。此时可dump出堆栈的消息,分析哪个方法的执行时间过长。

如何获取卡顿的方法堆栈信息?

直接调用系统函数。性能消耗小,不过只能获取简单的信息。

或者,直接使用第三方PLCrashReporter来获取堆栈信息,能获取到具体代码问题的位置,消耗也不大。

参考地址:代码

前排安利另一个聚聚写的RunLoop:深入理解RunLoop

这次想起以前遇到过的卡顿效果,真的是坑啊。

印象最深的一次卡顿:在TableView的Cell里访问了数据库。

其次是在一个答题系统里,多选单选与判断题,一个Cell从最少两个选项、一个选项三到N行,到最多九个选项。然后使用了TableViewCell的高度自动计算,后来换成了自己手动计算并缓存。

还有Cell的样式太多,后来把尽可能相同的都放到一个Cell里,也就是:一个Cell有七行,如果有四行不显示,那就移除四行,如果需要九行就再加两行。还有就是网络请求同步操作,结果数据过大,然后就做了异步和无数据的默认显示。

其实我就是从这几个方面下手的:页面重叠部分、简单的动画与显示用CALayer自己画、对象释放与创建的次数、高度自动缓存、autoRelease使用、static使用部分(如单列对象)、UI刷新前的数据处理部分、页面刷新次数、通知和监听的移除、数据的不合理读取、复杂操作尽量异步、

最后想说下:线程的消息传递依赖于NSRunLoop,函数的调用依赖runtime。感觉二者挺像的。

iOS开发高手笔记
上一篇 下一篇

猜你喜欢

热点阅读