离屏渲染

1、Cell 的重用

#示例1:常见用法

1
2
3
4
5
6
7
8
9
10
11
12
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
return cell;
}

[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 就是 cell 重用的标志性语句。上面的代码逻辑是:当需要显示一个 Cell 时,系统会先去重用队列中查询是否有可重用的 cell,如果有则复用它,如果没有则新建一个 cell。

#示例2:读取 UITableView 的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
+ (void)ivarList
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([UITableView class], &count);

for (int i = 0; i<count; i++){
Ivar ivar = ivars[i];
const char *charName = ivar_getName(ivar);
const char *charType = ivar_getTypeEncoding(ivar);
NSLog(@"Name:%s,Type:%s",charName,charType);
}
free(ivars);
}

上面通过运行时函数 class_copyIvarList 来读取 UITableView 中所有成员变量。结果如下:

1
2
Name:_visibleCells,Type:@"NSMutableArray"
Name:_reusableTableCells,Type:@"NSMutableDictionary"

从打印结果可以看到,UITableView 中包括了以下两个属性:

  • visibleCells:(保存当前屏幕范围内显示的 cells)
  • reusableTableCells:(保存可重用的 cells)

Cell的重用逻辑:

(1)假如当前屏幕能显示10个 Cell,在 UITableView 开始渲染时,系统会通过 -initWithStyle:reuseIdentifier: 方法创建10个新的 cell (标志相同) 并显示,这些 cell 会被放入 visibleCells 这个数组中。

(2)向下滑动 TableView,当 cell1 完全移出屏幕,并且 cell11 (它也是alloc出来的,原因同上)完全显示出来时,cell11 会被加入到 visiableCells,而 cell1 则被移出 visiableCells 并加入到 reusableTableCells 中。

(3)接着向下滑动 TableView,因为 cell1 已经在 reusableTableCells 中,所以 即将显示的第12个 cell 会重用 cell1。因此 cell1 会被移出 reusableTableCells 并加入到 visiableCells 中;同时 cell2 移出 visiableCells 并加入到 reusableTableCells 中。

(4)接下来的重用以此类推。。

注意: cell 是重用的,所以在配置 cell 时一定要重新设置重用的 cell,不要遗留老的数据。

2、卡顿与离屏渲染

Tableview 使用中常会出现性能问题,其中会涉及到离屏渲染,下面这些博客里会有专业的分析,多看看,收益颇丰~

以下是从上面博客摘录的部分内容:

2.1 卡顿的原因

通常来说,计算机系统中 CPU、GPU、显示器是协同工作的:CPU 负责计算显示的内容,如视图的创建、布局计算、图片解码、文本绘制等,完成后提交到 GPU;GPU 负责对 CPU 提交的内容进行变换、合成、渲染,完成后将渲染结果放入帧缓冲区;视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。

如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。

2.2.离屏渲染

#离屏渲染: 指的是GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作;

#当前屏幕渲染: 是指GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。

离屏渲染对性能影响很大,主要是因为离屏渲染会创建新的缓冲区,且在离屏渲染的整个过程,需要多次切换上下文环境。这些操作的开销很大(涉及到 OpenGL 的 pipelines 和 barrier 等),尤其是当有大量离屏渲染的情况时。

#为什么会有离屏渲染机制?

有些效果被认为不能直接呈现于屏幕,而需要在别的地方做额外的处理预合成。图层属性的混合体没有预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。

#会触发离屏渲染的场景:

  • 为图层设置遮罩(layer.mask);
  • 将图层的 layer.masksToBounds 或 view.clipsToBounds 设置为YES;
  • 将图层 layer.allowsGroupOpacity 设置为YES 且 layer.opacity小于1.0;
  • 为图层设置阴影(layer.[shadowRadius\shadowOffset\shadowOpacity]);
  • 为图层开启光栅化:layer.shouldRasterize = YES;
  • 为图层设置圆角 layer.cornerRadius;
  • 为图层设置抗锯齿性:layer.allowsEdgeAntialiasing = YES;
  • 文本(任何种类,包括UILabel,CATextLayer,Core Text等);
  • 使用 CGContext 在 drawRect :方法中绘制大部分情况下会导致离屏渲染(使用CPU渲染)。

#优化方案:

  • 使用中间透明,四个角有背景色的图片代替圆角效果;
  • 使用UIBezierPath给CAShapeLayer画圆角,再赋值给视图的layer.mask;
  • 使用 ShadowPath 提前告诉CoreAnimation待渲染图层的形状(layer.shadowPath = [UIBezierPath bezierPathWithRect:layer.bounds]);
  • 设置 layer.opaque = YES,减少复杂图层合成;
  • 尽量使用不包含透明(alpha)通道的图片资源;
  • 尽量设置 layer 的大小值为整型值。
  • 使用异步进行 layer 渲染(如 Facebook的AsyncDisplayKit框架);

相关参考:

#©ibireme-保持界面流畅的技巧

#©于海明-离屏渲染

#©Texture-Corner Rounding


离屏渲染
https://davidlii.cn/2018/01/20/offscreen.html
作者
Davidli
发布于
2018年1月20日
许可协议