iOS 之 UITableView 性能优化 (一)

 

引起 UITableView 卡顿比较常见的原因有 cell 的层级过多、subView alpha 为0,cell 中有触发离屏渲染的代码(譬如:cornerRadius、maskToBounds 同时使用)、像素是否对齐、是否使用 UITableView 自动计算 cell 高度的方法等。

不可否认的是,过早的优化是魔鬼,个人建议请在项目出现性能瓶颈再考虑优化。在需求未定,性能问题不明显时,没必要尝试做优化,而要尽量正确的实现功能。做性能优化时,也最好是走修改代码 -> Profile -> 修改代码这样一个流程,优先解决最值得优化的地方。

如果你需要一个明确的 FPS 指示器,可以尝试一下:FPSLabel 只有几十行代码,仅用到了 CADisplayLink 来监视 CPU 的卡顿问题。

让出主线程,让主线程减负

UIKit 的工作基本上都是在主线程上进行,界面绘制,用户输入响应等等。当所有的代码逻辑都放在主线程时,某些耗时任务可能会卡住主线程造成程序无法响应,流畅度降低等问题。所谓 “让出” 主线程,指的是不要什么操作都放在主线程里。放在主线程中的一般都是视图相关的操作,比如网络请求、数据读写等。

1
2
3
4
5
6
7
8
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//处理一些跟当前视图没关系的事情

//只用来操作与当前视图有关系的事情,比如:刷新 Cell
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reloadData];
});
});

正确重用 cell

1
2
3
4
5
static NSString *identifier = @"testcell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}

上面的代码在有 Cell 可重用的时候,便不会再创建新的 Cell,但是下面的一句话基本上会让重用粉身碎骨。

1
2
3
4
[cell.contentView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
[obj removeFromSuperview];
}];

在初始化 Cell 的时候就将所有需要展示的添加完毕,然后根据需要来设置 hide 属性显示和隐藏,不要在 Cell 中有添加或者删除子视图的操作。

subViews 设置为不透明

如果你有不透明的 Views,你应该设置它们的 opaque 属性为 YES。

原因是这会使系统用一个最优的方式渲染这些 views,这个简单的属性在 XIB、SB 或者代码里都可以设定。

Apple 的文档对于为图片设置不透明属性的描述是:

(opaque)这个属性给渲染系统提供了一个如何处理这个 View 的提示。如果设为 YES, 渲染系统就认为这个 View 是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为 NO,渲染系统正常地和其它内容组成这个 View。其默认值是就是 YES。

在相对比较静止的画面中,设置这个属性不会有太大影响。然而当这个 View 嵌在 ScrollView 里边,或者是一个复杂动画的一部分,不设置这个属性的话会在很大程度上影响 App 的性能。

可以在模拟器中用 Debug -> Color Blended Layers 选项来发现哪些 View 没有被设置为 opaque,能设为 opaque 的就全设为 opaque。

不要使用 ClearColor,无背景色,透明度也不要设置为0,如果界面中含有透明图层,那么就找 UI 要一张透明的 png 图像替代。

避免过于庞大的 XIB

如果是复杂且需要高效的界面, 尽量将 SB 和 XIB 创建的界面改为代码创建, 减轻系统需要进行 XML 转码操作的负担。如果非要用 XIB 的话,尽量保证一个 XIB 文件中只有一个视图, 因为加载一个 XIB 的时候, 所有的内容都被放在内存里, 包括图片素材, 如果有一个不会即刻用到的视图, 那么就是在浪费我们宝贵的内存了, 并且过于庞大的 XIB 会延长 App 的启动时间, 所以没有必要的话, 最好在 SB 中创建视图。

避免日期格式转换

如果你要用 NSDateFormatter 来处理很多日期格式,应该小心以待。就像先前提到的,任何时候重用NSDateFormatters 都是一个好的实践。

如果你可以控制你所处理的日期格式,尽量选择 Unix 时间戳。你可以方便地从时间戳转换到 NSDate,这样会比用 C 来解析日期字符串还快。

1
2
3
- (NSDate *)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
return [NSDate dateWithTimeIntervalSince1970:timestamp];
}

渲染

减少 Subviews 的个数和层级关系,子控件的层级越深,渲染到屏幕上所需要的计算量就越大;多用 drawRect 绘制元素,替代用 View 显示。

1
2
3
4
- (void)drawRect:(CGRect)rect { 
[image drawAtPoint:imagePoint];
[text drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation];
}

避免 CALayer 特效:给 Cell 中 View 加阴影会引起性能问题,比如下面代码会导致滚动时有明显的卡顿:

1
2
3
4
view.layer.shadowColor = color.CGColor;
view.layer.shadowOffset = offset;
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = radius;