1.卡顿原理
2.卡顿监测
2.1 YYKit CADisplayLink
YYKit的实质是1/fps = 时间/count fps = count/delta
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
float fps = _count / delta;
_count = 0;
如果fps都为60,说明不发生卡顿,如果fps越是小60,说明卡顿越明显
2.2runloop 监测
iOS通过提交事物,事物再唤起runloop进行处理,我们监听runloop的状态可以知道我们提交的事物运行的时间
{
[self registerObserver];
[self startMonitor];
}
static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
LGBlockMonitor *monitor = (__bridge LGBlockMonitor *)info;
monitor->activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = monitor->_semaphore;
dispatch_semaphore_signal(semaphore);
}
- (void)registerObserver{
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
//NSIntegerMax : 优先级最小
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
NSIntegerMax,
&CallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
- (void)startMonitor{
// 创建信号
_semaphore = dispatch_semaphore_create(0);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES)
{
// 超时时间是 1 秒,没有等到信号量,st 就不等于 0, RunLoop 所有的任务
long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
if (st != 0)
{
if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
{
if (++self->_timeoutCount < 2){
NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
continue;
}
// 一秒左右的衡量尺度 很大可能性连续来 避免大规模打印!
NSLog(@"检测到超过两次连续卡顿");
}
}
self->_timeoutCount = 0;
}
});
}
2.2.1微信的监测工具
实质也是判断runloop状态间的时候 matrix-wechat
3.实战优化
3.1.预排版+内存缓存
我们在用tableView和UICollectionView显示内容时,有时会出现复杂布局的Cell,为了优化性能,我们可以把布局数据计算好,在加载时直接显示,很好的优化性能增强用户体验。
3.2预解码渲染
我们通过下面代码加载图片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1606640873&di=ea6988d5033854f0d3bcebfe4404d890&src=http://a4.att.hudong.com/27/67/01300000921826141299672233506.jpg"]];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
self.myImageView.image = image;
});
});
image.png
我们把对图片的解码放到子线程中,对图片的解码可以参看SDWebimage中的SDWebImageDecoder
3.3按需加载
只有在需要显示的地方加载图片,比如我们快速滑动tableView,我们只在停止的位置加载cell,滑动过程的cell不优先加载。
3.4异步渲染
view和layer的关系参照iOS图形渲染流程
ViewAndLayer.png
异步渲染,可以参看美团的Graver