iOS 性能优化 — 二、卡顿监控及处理

用户头像
iOSer
关注
发布于: 2020 年 10 月 23 日
iOS性能优化 —  二、卡顿监控及处理



上篇文章为大家讲解了crash监控及防崩溃处理,这片文章继续为大家讲解下卡顿监控及处理。



  • 卡顿产生原理

  • 如何收集卡顿

* 利用bugly、听云等第三方收集

* 自己收集卡顿

* 监控主线程RunLoop

* 子线程ping



卡顿产生原理



FPS (Frames Per Second) 表示每秒渲染帧数,通常用于衡量画面的流畅度,每秒帧数越多,则表示画面越流畅。通常60是临界值,如果主线层FPS低于60fps,应用程序就可能产生卡顿。大家可以看这篇文章详细了解卡顿产生原理。



如何收集卡顿



利用bugly、听云等第三方收集



国内有很多第三方网站可以用来收集卡顿,常用的有bugly、听云等。笔者推荐大家用腾讯的bugly来收集卡顿。



自己收集卡顿



如果我们要自己手动监控卡顿,其实有好几种方案,如下:



监控主线程RunLoop



我们知道iOS App基于RunLoop运行,我们先来看看RunLoop简化后的代码。



// 1.进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)

// 2.RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3.RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 4.RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle)
// 5.执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);

// 6.RunLoop 的线程即将进入休眠(sleep)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);

// 7.调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)

// 进入休眠

// 8.RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting

// 9.如果一个 Timer 到时间了,触发这个Timer的回调
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())

// 10.如果有dispatch到main_queue的block,执行bloc
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

// 11.如果一个 Source1 (基于port) 发出事件了,处理这个事件
__CFRunLoopDoSource1(runloop, currentMode, source1, msg);

// 12.RunLoop 即将退出
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);




我们可以看到RunLoop调用方法主要集中在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之间。我们可以开辟一个子线程来监控主线程RunLoop,然后实时计算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 两个状态区域之间的耗时是否超过某个阀值,来断定主线程的卡顿情况,比如如果连续5次超时50ms,则认为发生了卡顿。代码如下:



@interface AKStuckMonitor ()
{
int timeoutCount;
CFRunLoopObserverRef observer;

@public
dispatch_semaphore_t semaphore;
CFRunLoopActivity activity;
}

@end

@implementation FQLAPMStuckMonitor

+ (instancetype)sharedInstance{

static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){

AKStuckMonitor *moniotr = (__bridge AKStuckMonitor*)info;

moniotr->activity = activity;

dispatch_semaphore_t semaphore = moniotr->semaphore;
dispatch_semaphore_signal(semaphore);
}

- (void)stop{

if (!observer)
return;

CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
observer = NULL;
}

- (void)start{

if (observer)
return;

// 信号
semaphore = dispatch_semaphore_create(0);

// 注册RunLoop状态观察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
float time = 50;
while (YES)
{
long st = dispatch_semaphore_wait(self->semaphore, dispatch_time(DISPATCH_TIME_NOW, time * NSEC_PER_MSEC));
if (st != 0)
{
if (!self->observer)
{
self->timeoutCount = 0;
self->semaphore = 0;
self->activity = 0;
return;
}

if (self->activity==kCFRunLoopBeforeSources || self->activity==kCFRunLoopAfterWaiting)
{
if (++self->timeoutCount < 5)
continue;
NSlog(@"检测到卡顿");

}
}
self->timeoutCount = 0;
}
});
}

@end




子线程ping



创建一个子线程通过信号量去ping主线程,每次检测时设置标记位为YES,然后派发任务到主线程中将标记位设置为NO。接着子线程沉睡超时阙值时长,判断标志位是否成功设置成NO,如果没有说明主线程发生了卡顿。



@interface PingThread : NSThread
......
@end

@implementation PingThread

- (void)main {
[self pingMainThread];
}

- (void)pingMainThread {
while (!self.cancelled) {
@autoreleasepool {
dispatch_async(dispatch_get_main_queue(), ^{
[_lock unlock];
});

CFAbsoluteTime pingTime = CFAbsoluteTimeGetCurrent();
[_lock lock];
if (CFAbsoluteTimeGetCurrent() - pingTime >= _threshold) {
......
}
[NSThread sleepForTimeInterval: _interval];
}
}
}
@end

资料推荐



如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群1012951431来获取一份详细的大厂面试资料为你的跳槽多添一份保障。





用户头像

iOSer

关注

微信搜索添加微信 mayday1739 进微信群哦 2020.09.12 加入

更多大厂面试资料进企鹅群1012951431

评论

发布
暂无评论
iOS性能优化 —  二、卡顿监控及处理