iOS设备虽然在硬件和软件层面一直在优化,但还是有不少坑会导致UI线程的卡顿。对于程序员来说,除了增加自身知识储备和养成良好的编程习惯之外,如果能一套机制能自动预报“卡顿”并检测出导致该“卡顿”的代码位置自然更好。本文就可能的实现方案做一些探讨和分析。先贴出最后方案的github地址。
解决方案分析
简单来说,主线程为了达到接近60fps的绘制效率,不能在UI线程有单个超过(1/60s≈16ms)的计算任务。通过Instrument设置16ms的采样率可以检测出大部分这种费时的任务,但有以下缺点:
Instrument profile一次重新编译,时间较长。
只能针对特定的操作场景进行检测,要预先知道卡顿产生的场景。
每次猜测,更改,再猜测再以此循环,需要重新profile。
我们的目标方案是,检测能够自动发生,并不需要开发人员做任何预先配置或profile。运行时发现卡顿能即时通知开发人员导致卡顿的函数调用栈。
基于上述前提,我暂时能想到两个方案大致可行。
方案一:基于Runloop
主线程绝大部分计算或者绘制任务都是以Runloop为单位发生。单次Runloop如果时长超过16ms,就会导致UI体验的卡顿。那如何检测单次Runloop的耗时呢?
Runloop的生命周期及运行机制虽然不透明,但苹果提供了一些API去检测部分行为。我们可以通过如下代码监听Runloop每次进入的事件:
- (void)setupRunloopObserver{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{CFRunLoopRefrunloop =CFRunLoopGetCurrent();CFRunLoopObserverRefenterObserver; enterObserver =CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopEntry | kCFRunLoopExit,true,-0x7FFFFFFF, BBRunloopObserverCallBack,NULL);CFRunLoopAddObserver(runloop, enterObserver, kCFRunLoopCommonModes);CFRelease(enterObserver); });}staticvoidBBRunloopObserverCallBack(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity,void*info) {switch(activity) {casekCFRunLoopEntry: {NSLog(@"enter runloop..."); }break;casekCFRunLoopExit: {NSLog(@"leave runloop..."); }break;default:break; }}
网友评论