Swift工程添加卡顿监测
POCT项目在现阶段,越来越注意App性能方面的内容,前段时间大佬拿了一台新鲜出炉的iPhoneX,主要拿来做适配。大佬偶然发现日历页面的滑动返回,感觉会有卡顿情况,于是让不用跟进项目进度的我,看看什么原因,给出解决方案。
拿到手之后,先用Instrument查看了一下返回阶段相对耗时的event,大概定位到了位置。接下来的事情应该是查看对应方法,找到可以修改的内容,写个文档说明,交公。想法是美好的,但是实际上神经大条的我,诚惶诚恐的拿到iPhoneX之后,慢慢的来回滑动返回页,并没有很明显的感知到卡顿现象。WTF!!大佬既然说卡,那卡必然是存在的,但每个人对卡顿的感觉不一致。数据化卡顿时间,可以作为支持卡顿支撑点。
秉承想要将卡顿现象数据化的想法,对POCT的工程进行卡顿监测,也不用大佬们,拿着手机追在我屁股后面跟我说这里卡了,好好看看,好好改改,时时刻刻处于担惊受怕的状态中。通过实现POCT的卡顿监测,可以真实的反应POCT项目出现卡顿的所有情况,有计划的一一处理卡顿情况。让有问题的code无处遁形~
卡顿造成的原因
本文主要讲的是添加卡顿监测,但是在编写监测卡顿工具时,需要知道在“某个时间间隔”内停留在“某个方法”中即算造成了卡顿。在下面的介绍中,将会对“某个时间间隔”进行简单的说明,对之后的具体的时间间隔引入提供实际依据。
iOS图形呈现的原理如下图所示:
Vsync:信号,信号到达后如果缓冲区内有内容,就显示;如果缓冲区内没内容就会出现掉帧。
CADisplayLink:系统图形服务会通过通过 CADisplayLink机制通知App。
CPU:视图创建、布局计算、图片解码、文本绘制等。
GPU:变换、合成、渲染完成后将内容提交到缓冲区。
按照上述的概念,VSync信号的到达频率即上述所说的“某个时间间隔”,如果CPU+GPU的处理时间超过时间间隔即会有掉帧即卡顿情况。iOS中Vsync的刷新频率是0.01666秒,在设计卡顿检测工具中,会将该时间间隔作为单个任务处理的时间上限,超过此时间上限即视为卡顿。
关于时间间隔的说明如果有错误之处,可以进行说明。
以RunLoop作为切入点
主线程和Runloop是一一对应关系,Runloop控制着主线程的运作。反过来,如果主线程因为出现大量计算或者布局而导致卡顿的发生,Runloop肯定能够感知。以Runloop各个阶段运行的时间间隔,作为监测线程卡顿的依据这个想法貌似是可行的。下面将以上述想法作为切入点,对线程进行实时监测,逐一找到POCT中造成卡顿的操作。
通过阅读RunLoop的源码CFRunLoopRun方法可以知道UI的刷新主要是
kCFRunLoopBeforeSources-> kCFRunLoopBeforeWaiting
kCRunLoopAfterWaiting之后 所以如果在这两个阶段耗时超过0.0166秒的话,就可以判定主线程有卡顿情况。
通过信号量来保证来确保获取到主线程的状态。
下面代码是将时间间隔设置为0.03334秒(30帧)来obsever主线程,突出明显的卡顿现象。
因为一般viewWillAppear到viewDidAppear需要0.04秒,如果启动阶段会出现1次以上的提示,表示该页面的启动是存在卡顿的。
funcsetRunloopObserver(){
letrunloop =CFRunLoopGetCurrent()
letunmanaged =Unmanaged.passRetained(self)
letuptr = unmanaged.toOpaque()
letvptr =UnsafeMutableRawPointer(uptr)
varcontent =CFRunLoopObserverContext(version:0, info: vptr, retain:nil, release:nil, copyDescription:nil)
enterObserver =CFRunLoopObserverCreate(kCFAllocatorDefault,CFRunLoopActivity.allActivities.rawValue,true,0, callBackObserve(), &content)CFRunLoopAddObserver(runloop, enterObserver,CFRunLoopMode.commonModes)
sema =DispatchSemaphore(value:0)letqueue =DispatchQueue.global()
queue.async {
//循环while(true) {
//等待时间0.033334秒,超过时间也继续执行
let st =self.sema?.wait(timeout:DispatchTime.now() +0.033334)
//如果超时执行st == .timeOut,非超时执行.success
if st == .timedOut {
ifself.activityEnum == .afterWaiting ||self.activityEnum == .beforeSources {
//此部分功能需要丰富
print("-------------\(UIViewController.topMostViewController()) -------------")
}
}
}
}
}
privatefunccallBackObserve()->CFRunLoopObserverCallBack{
return{ (observer, activity, context)in
letwfSelf =Unmanaged.fromOpaque(context!).takeUnretainedValue()
//获取到当前的activitywfSelf.activityEnum = activity
//完成了一次观察,添加信号量初始化信号量为0
wfSelf.sema?.signal()
}
}
在不使用的时候将observer移除,减少不必要的内容消耗。
funcremoveRunloopObserver(){
ifenterObserver ==nil{
return
}
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), enterObserver, CFRunLoopMode.commonModes)
}
在AppDelegate中调用setRunloopObserver方法,即可实现对POCT工程的卡顿监测。
CatonMonitoring.sharedInstance.setRunloopObserver()
获取POCT项目的卡顿点
关于日历页面返回手势卡顿的情况之前在别的机型上测试并没有明显的感觉。本以为可能是卡顿的情况相对小,但实际上通过卡顿监测还是能发现。然后在实际的测试过程中,用iPhone6对日历页面的返回手势进行反复操作,监测工具并没有出现提示。WTF?高贵的iPhoneX得到什么病?这周工作内容:无!😢.
虽然返回手势操作没有监测到卡顿情况,但是在POCT项目运行的过程中还是发现了以下几个问题:
HomePageController(首页)卡片控件中点击建议详情会出现卡顿情况。
RecordController(日历页) 点击“来姨妈了”SwitchButton,显示红色周期,会有卡顿情况。点击“爱爱”SwitchButton,对应的显示不会出现卡顿情况。
MinePhysiologicalInfoController(生理资料页),点击“月经是否规律”SwitchButton,显示后面的两个Cell会出现卡顿。
ExamineRecordController(检测记录页)右滑刷新操作,会造成卡顿。
弹出框的显示会出现卡顿。
之后会对其中出现的问题一一排查。如有必要,会在之后的文档中详细说明。
总结
关于卡顿监测工具在性能上对POCT的影响还需要进一步做测试,而且也没有对卡顿造成的具体位置进行定位。在后期会对某些第三方库进行调研,使工具能够确保将卡顿的原因成功上报。
如果文章内容有问题的地方,欢迎提问。