博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS性能监控
阅读量:6271 次
发布时间:2019-06-22

本文共 2876 字,大约阅读时间需要 9 分钟。

卡顿

通过观察者注册到RunLoop可以在以下几个阶段收到通知

kCFRunLoopEntry

kCFRunLoopBeforeTimers

kCFRunLoopBeforeSources

kCFRunLoopBeforeWaiting

kCFRunLoopAfterWaiting

kCFRunLoopExit

kCFRunLoopAllActivities

__CFRunLoopRun具体源码分析可见,根据源码提炼出Runloop循环的主要流程如下图:

RunLoop处理的事件源归类

RunLoop处理的事件源总体其实可以分为两大类,第一类的source0,第二类类是基于MachPort的消息。

平时UIView的事件处理以及代码中定义的block调用都属于source0,而线程之间的消息通讯,通过GCD派

发到主线程的任务以及NSTimer都是基于MachPort消息实现的。所以在流程图中,我将第二类进行了

突出,表示它们是一大类型。在官方文档中,它分为timer,source,Oberserver,source又分

为source0和source1,单独将timer拿出来。

RunLoop的哪些状态值得被关注

source0的任务执行前 RunLoop处于的状态是KCFRunLoopBeforeSources

基于machPort的任务执行前 RunLoop处于的状态是kCFRunLoopBeforeSources或者

kCFRunLoopAfterWaiting所以只需要监听Runloop的状态在CFRunLoopBeforeSources和

CFRunLoopAfterWaiting这两个状态是否产生了制定时间的执行时间就可以判断是否发生卡顿了。

而CFRunLoopBeforeWaiting之后,Runloop就进入睡眠了,没有处理其他事件源的逻辑。

如果监控该状态,在主线程睡眠的时候,就会判断为卡顿了,产生误报。

监控主线程Runloop的状态变化

开启一个子线程,让子线程去检测主线程Runloop的状态变化。在每次Runloop状态发生变化的时候,通过

dispatch_semaphore_signal发送一个信号,使得信号量的值加1.让子线程一直关注semaphore的变化,

如果主线程在Runloop某一个状态停留太久,那么semaphore会让子线程阻塞,直到超时,即发生了卡顿。

//全局或者成员变量CFRunLoopActivity currentActivity;​//信号量dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);​//检测主线程Runloop状态CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer,  CFRunLoopActivity activity) { if (self != nil && semaphore != NULL) { currentActivity = activity; dispatch_semaphore_signal(semaphore); } }); CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes); CFRelease(observerRef);​//通过信号量计算某一状态下是否执行超过制定时间,这里是1swhile (!self.cancelled) { long status = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW,  1000 * NSEC_PER_MSEC)); if (status != 0) { if (self.cancelled || semaphore == NULL) { return; } if (currentActivity == kCFRunLoopBeforeSources ||  currentActivity == kCFRunLoopAfterWaiting ) { if (self.cancelled) return; //发生了卡顿 NSLog(@"卡顿了"); if (self.cancelled) return; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } } }复制代码

最后用一张图来展示整个流程:

FPS

CADisplayLink是与屏幕刷新保持同步的定时器,通过它可以计算出实时的屏幕帧率。

在理想情况下,每秒60帧,CADdisplayLink回调的时间间隔就是1/60 ≈ 0.0167秒。

在runloop每次迭代的过程中,如果有耗时的任务,会增加CADdisplayLink回调时长,相对来说,计算出来

的帧率就会较小。屏幕绘制的时候,发生跳帧。

CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(p_tick:)];[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];​- (void)p_tick:(CADisplayLink *)link{ self.count++; if (!self.lastTime) { self.lastTime = CACurrentMediaTime(); } else { NSTimeInterval currentTime = CACurrentMediaTime(); NSTimeInterval duration = currentTime - self.lastTime; if (duration > 1) { NSString *fps = [NSString stringWithFormat:@"%.2f", (self.count / duration)]; self.FPSLabel.text = [NSString stringWithFormat:@"帧率 : %@", fps]; self.count = self.lastTime = 0; } }}复制代码

转载于:https://juejin.im/post/5b6818c8f265da0f4c6fe19a

你可能感兴趣的文章
Weblogic classloader分析
查看>>
做技术做软件-----如何才能拿到上万的月薪
查看>>
linux 查看当前路径命令:pwd
查看>>
At.js – 用于 Web 应用程序的自动完成库
查看>>
[Android Pro] Android权限设置android.permission完整列表
查看>>
如何对抗硬件断点--- 调试寄存器
查看>>
mybatis学习
查看>>
从不同层面看cocos2d-x
查看>>
Struts2技术详解
查看>>
MFC应用程序向导生成的文件
查看>>
Oracle体系结构之oracle密码文件管理
查看>>
【leetcode】Remove Element (easy)
查看>>
mysql多表查询及其 group by 组内排序
查看>>
alsa的snd_pcm_readi()函数和snd_pcm_writei()
查看>>
Android学习网站推荐(转)
查看>>
嵌入式根文件系统的移植和制作详解
查看>>
MEF部件的生命周期(PartCreationPolicy)
查看>>
LCD的接口类型详解
查看>>
nginx 基础文档
查看>>
LintCode: Unique Characters
查看>>