问题
我们使用的是一个 UITableView
套用 UICollectionView
的一个结构,在 iPhone 6 上 tableView
的上下滚动还会造成明显的卡顿。但事实上 FPS 没有怎么下降。FPS 不能作为卡顿时间的目标
基础知识
iOS 显示原理
借用下 ibireme 大神的一张图
1、 CPU 计算显示内容提交给 GPU ,如:视图的创建、布局计算、图片解码、文本绘制
2、 GPU 渲染后将结果放入 FrameBuffer (帧缓冲区)
FrameBuffer 中存放的是你在屏幕上看到的所有像素的颜色值和半透明值(RGBA)
3、 视频控制器按照 VSync(垂直同步信号) 读取 FrameBuffer 显示的屏幕上
罪魁祸首 ?掉帧
如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。
— ibireme
再借一张(逃
问题查询
初步分析以及解决方案
从 Time Profiler 的结果上分析可以看到
-
[AMAppTitleLabel textRectForBounds:limitedToNumberOfLines:]
以及[AMAppTitleLabel drawTextInRect:]
消耗了大部分 cpu 计算时间,其实这里我们为了一个效果重写了一个DrawRect
方法导致的,改为计算 frame 的方式来实现同样的效果。 -
[AMMainFrameViewModel configureCell:forRowAtIndexPath:]
这是UITableView
delegate- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
中每一行调用的方法, 看下具体的实现 大部分的 CPU 的时间消耗在addSubView
上,这其实和下面一条 AutoLayout 是相关的。还有之后的[UIView(Extend) removeAllSubviews]
。尽量将 cell 做成单一可重用的,避免在滚动中的添加和移除操作。 -
[MASViewConstraint install]
AutoLayout 很大提升了我们开发的效率,但是对于相对复杂的视图会带来严重的性能问题。 AutoLayout 根据给定的约束去计算 frame 再设置,和人为的计算好 frame 直接设置,结果可想而知。pilky.me 所以对 2、3 两点的优化方案就是 尽量减少视图层级,移除 AutoLayout 改为手动设置 frame。
GPU 问题
以上基本是 CPU 的计算问题, 下面来说说 GPU 的消耗
图片加载过程
大部分还都是 CPU 的活
// 伪代码
UIImage *img = [[UIImage alloc] initWithContentsOfFile:path];
UIImageView.image = img;
// UIImageView.layer.contents = img.CGImage;
[UIImageView moveToWindow];
if ([UIImageView.layerTree changed]) {
CGContextRef bitmap;
if ([img isPNG] || [img isJPG]) {
bitmap = [CPU tryToDecode];
}
else {
bitmap = img.CGImage;
}
if (![bitmap isByteAlign]) {
bitmap = CA::Render::copy_image(bitmap);
}
[GPU drawBitmap:bitmap];
}
- 从磁盘拷贝数据到内核缓冲区
- 从内核缓冲区复制数据到用户空间
- 生成UIImageView,把图像数据赋值给UIImageView
- 如果图像数据为未解码的PNG/JPG,解码为位图数据
- CATransaction捕获到UIImageView layer树的变化
- 主线程Runloop提交CATransaction,开始进行图像渲染
6.1 如果数据没有字节对齐,Core Animation会再拷贝一份数据,进行字节对齐。
6.2 GPU处理位图数据,进行渲染。
– bang
视图叠加
alpha = 1
alpha = 0.5
frameBuffer 里都是都是展开的 RGBA, 视图叠加的操作就是对 S 和 D RGBA 的操作。
公式
R = S + D * ( 1 – Sa )
套用 alpha = 0.5 时整个公式的计算结果如下
S RGBA (1,0,0,0.5)
D RGBA (0,0,1,1)
0.5 0 0.5
R = S + D * (1 - Sa) = 0 + 0 * (1 - 0.5) = 0
0 1 0.5
离屏渲染
- border、圆角、阴影、遮罩(mask) 触发离屏渲染
- 另外开辟一块缓冲区(OffScreenBuffer)来做渲染的操作
- CALayer.shouldRasterize 光栅化,将离渲染的结果保存为位图(bitmap)存储起来复用,从而减少离屏渲染的次数
为什么要避免离屏渲染
GPU 平时要做的
- 在 frameBuffer 里渲染好需要显示的内容
GPU 在离屏渲染时要做的
- 从 frameBuffer 跑到 OffScreenBuffer 渲染好离屏部分内容
- 将 OffScreenBuffer 中的内容合并到 frameBuffer
离屏幕渲染时需要更多的操作
- 开辟了新的缓冲区 OffScreenBuffer
- 渲染内容的合并
- 2 次昂贵的环境切换
横向滚动卡顿
优化了这些问题后还是有点卡顿?横向滚动卡顿。AppStore 做了神马操作? 逆向 AppStore 看看。过程比较复杂可以另外写一篇记录了。
逆向时发现还有这么几个点:
App icon 圆角的绘制
从 iOS 7 开始,App 的 icon 改进了一次图标的圆角。
// 并不是这么实现的
layer.cornerRadius = 5.0f;
- 由 A 反向蒙板生成 B;
- 以 B 为蒙板生成圆角 C;
- 将 D 和 C 绘制生成一张带边框的图片;
- 将处理后图片缓存起来;
进一步优化
1.异步绘制
AppStore 在未加载完时仍会卡顿,未实现异步绘制。 应为 CoreGraphic 线程安全,异步绘制也较容易实现。
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
2.可取消的绘制
当 cell 滑出屏幕时未取消该 cell 上绘制,SDWebImage 有实现取消加载的操作。简单的实现方式就是 在每次绘制前,判断是否已取消
- (void)_backgroundDrawImageForImage:(UIImage *)image cacheWithUrl:(id)url
completed:(AMUIImageCompletionBlock)complete
{
NSString *key = [[self class] _keyForImageUrl:url];
if ([self _isDrawingCanceled:key]) {
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
if ([self _isDrawingCanceled:key]) {
return;
}
// Draw image
if ([self _isDrawingCanceled:key]) {
return;
}
UIImage *iconImg = UIGraphicsGetImageFromCurrentImageContext();
if (complete) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self _isDrawingCanceled:key]) {
}
else {
complete(iconImg, nil, url);
}
});
}
});
}
滚动时的阻力
decelerationRate 0.899999976158; // 0x3f666666
- AppStore
- Custom
滚动停止时,加载图片。
另外的优化点
- 将计算布局的结果保存起来避免同样的内容多次计算;
- 减少视图层级,可绘制成1张图片显示;
- 将没有点击事件的
UIView
换成CALayer
实现; - 减少设置视图的
frame/bounds/transform
; - 去 Storyboard ,性能敏感界面直接代码创建 View 对象;
参考文章
- iOS 保持界面流畅的技巧 - ibireme
- 绘制像素到屏幕上
- WWDC心得与延伸:iOS图形性能 - 方秋枋
- iOS 视图—动画渲染机制探究 - 腾讯Bugly特约作者 陈向文
- 微信iOS卡顿监控系统
- iOS实时卡顿监控
- 微信读书 iOS 性能优化总结
- iOS 10 UITableView 和 UICollection View 新增 API
- iOS图片加载速度极限优化—FastImageCache解析 bang
-以上-