avatar

Catalog
iOS性能优化05-卡顿优化03-卡顿监测
  • 平时所说的”卡顿“主要是因为在主线程执行了比较耗时的操作阻塞了主线程造成的

  • 可以添加Observer到主线程Runloop中,通过监听Runloop状态切换的耗时,以达到监控卡顿的目的

下面我们来具体看看如何用代码实现

首先我们先看看Runloop的运行逻辑
Runloop运行逻辑

我们知道我们主线程大部分的操作(比如点击事件的处理、view的绘制计算等等)都是在source0source1之间,所以我们只要监控下结束休眠处理source1一直到绕回来处理source0这种所消耗的时间,如果发现这次Runloop所消耗的时间比较长,有可能就证明这些操作就比较耗时了,所以我们卡顿监测就是这样子一个效果,最好是能把导致耗时的代码是哪个方法监测出来,我们自己来写这个代码也是可以的,但是比较复杂,我从网上找了个写好的大家可以参考下源码

源码链接

我们跑下demo看下
我们滑动列表会发现卡顿并且在控制台有输出监测到导致卡顿的方法调用栈

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2020-03-01 22:42:24.696074+0800 LXDAppFluecyMonitor[8018:288367] Backtrace of Thread 771:
======================================================================================
libsystem_kernel.dylib 0x7fff523b8bba __semwait_signal + 10
libsystem_c.dylib 0x7fff52348352 sleep + 41
LXDAppFluecyMonitor 0x10bab858f -[ViewController tableView:cellForRowAtIndexPath:] + 351
UIKitCore 0x7fff48297462 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 781
UIKitCore 0x7fff4826043b -[UITableView _updateVisibleCellsNow:] + 3081
UIKitCore 0x7fff4828055f -[UITableView layoutSubviews] + 194
UIKitCore 0x7fff485784bd -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2478
QuartzCore 0x7fff2b131db1 -[CALayer layoutSublayers] + 255
QuartzCore 0x7fff2b137fa3 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 517
QuartzCore 0x7fff2b1438da _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 80
QuartzCore 0x7fff2b08a848 _ZN2CA7Context18commit_transactionEPNS_11TransactionEd + 324
QuartzCore 0x7fff2b0bfb51 _ZN2CA11Transaction6commitEv + 643
QuartzCore 0x7fff2afeb37f _ZN2CA7Display11DisplayLink14dispatch_itemsEyyy + 921
QuartzCore 0x7fff2b0c3e03 _ZL22display_timer_callbackP12__CFMachPortPvlS1_ + 299
CoreFoundation 0x7fff23b9503d __CFMachPortPerform + 157
CoreFoundation 0x7fff23bd4bc9 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
CoreFoundation 0x7fff23bd4228 __CFRunLoopDoSource1 + 472
CoreFoundation 0x7fff23bced64 __CFRunLoopRun + 2516
CoreFoundation 0x7fff23bce066 CFRunLoopRunSpecific + 438
GraphicsServices 0x7fff384c0bb0 GSEventRunModal + 65
UIKitCore 0x7fff48092d4d UIApplicationMain + 1621
LXDAppFluecyMonitor 0x10bab9a40 main + 112
libdyld.dylib 0x7fff5227ec25 start + 1

======================================================================================

我们看到代码中在:

Code
1
2
3
4
5
6
7
8
9
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: @"cell"];
cell.textLabel.text = [NSString stringWithFormat: @"%lu", indexPath.row];
if (indexPath.row > 0 && indexPath.row % 30 == 0) {
// usleep(2000000);
sleep(2.0);
}
return cell;
}

有人主动休眠了2s,那肯定会卡顿2s的导致的耗时

我们在didSelectRowAtIndexPath方法里再测试下

Code
1
2
3
- (void)tableView: (UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {    
sleep(2.0);
}

我们看到控制台也有输出卡顿的方法调用栈,

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
=====================================================================================
libsystem_kernel.dylib 0x7fff523b8bba __semwait_signal + 10
libsystem_c.dylib 0x7fff52348352 sleep + 41
LXDAppFluecyMonitor 0x10bab8640 -[ViewController tableView:didSelectRowAtIndexPath:] + 80
UIKitCore 0x7fff4827a42b -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:] + 855
UIKitCore 0x7fff4827a0bd -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 97
UIKitCore 0x7fff4827a8e8 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 334
UIKitCore 0x7fff4809be5b _runAfterCACommitDeferredBlocks + 352
UIKitCore 0x7fff4808c7b4 _cleanUpAfterCAFlushAndRunDeferredBlocks + 248
UIKitCore 0x7fff480bc3a9 _afterCACommitHandler + 85
CoreFoundation 0x7fff23bd3867 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
CoreFoundation 0x7fff23bce2fe __CFRunLoopDoObservers + 430
CoreFoundation 0x7fff23bce97a __CFRunLoopRun + 1514
CoreFoundation 0x7fff23bce066 CFRunLoopRunSpecific + 438
GraphicsServices 0x7fff384c0bb0 GSEventRunModal + 65
UIKitCore 0x7fff48092d4d UIApplicationMain + 1621
LXDAppFluecyMonitor 0x10bab9a40 main + 112
libdyld.dylib 0x7fff5227ec25 start + 1

======================================================================================

所以这个demo是完全可以监测卡顿的,那我们来简单看下代码的逻辑,方法调用栈的打印就不过多介绍了,因为比较复杂大家有兴趣可以参考源码里的Backtrack文件里的代码自己去研究下,接下来我们就看看作者的逻辑:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 首先创建observer,监听所有Runloop状态
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &lxdRunLoopObserverCallback, &context);
// 添加observer
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 创建循环监听Runloop各种状态下的耗时,如果超过预定的值就认为是卡顿并打印出来
dispatch_async(lxd_fluecy_monitor_queue(), ^{
while (SHAREDMONITOR.isMonitoring) {
long waitTime = dispatch_semaphore_wait(self.semphore, dispatch_time(DISPATCH_TIME_NOW, lxd_wait_interval));
if (waitTime != LXD_SEMPHORE_SUCCESS) {
if (!SHAREDMONITOR.observer) {
SHAREDMONITOR.timeOut = 0;
[SHAREDMONITOR stopMonitoring];
continue;
}
if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeSources || SHAREDMONITOR.currentActivity == kCFRunLoopAfterWaiting) {
if (++SHAREDMONITOR.timeOut < 5) {
continue;
}
[LXDBacktraceLogger lxd_logMain];
[NSThread sleepForTimeInterval: lxd_restore_interval];
}
}
SHAREDMONITOR.timeOut = 0;
}
});
}

喜欢的朋友可以扫描关注我的公众号(多多点赞,多多打赏,您的支持是我写作的最大动力)

iOS_DevTips

Author: 木子召
Link: https://lizhaobomb.github.io/2020/03/01/iOS%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%9605-%E5%8D%A1%E9%A1%BF%E4%BC%98%E5%8C%9603-%E5%8D%A1%E9%A1%BF%E7%9B%91%E6%B5%8B/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶

Comment