列表界面的卡顿优化,除了缓存计算高度,
还可以开个线程,异步计算高度并缓存,
再回到主线程,刷新界面
还有图片解码消耗性能, ...
-
使用纯代码,取代 xib ( xib, 一般会被解析成代码 )
-
减少 UI 显示层级, ( zIndex 方向上 )
-
合并用来显示的控件, ( UI 控件,数量上 )
例如:
一个 Label 的富文本 = 多个 Lable 的纯文本
- 避免离屏渲染
避免使用到,离屏渲染缓冲区
( 苹果已经,优化得很好了 )
本文重点介绍,多线程大法
对于复杂的列表显示,
一般都是,滚动的时候,更卡顿
- A 修改前,
A 特点 0,没有缓存高度
cell 的高度,触发直接计算
先渲染,再取高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
TimeLineCell *cell = [[TimeLineCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
[cell configureTimeLineCell:timeLineModel];
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
[cell setNeedsLayout];
[cell layoutIfNeeded];
CGFloat rowHeight = 0;
for (UIView *bottomView in cell.contentView.subviews) {
if (rowHeight < CGRectGetMaxY(bottomView.frame)) {
rowHeight = CGRectGetMaxY(bottomView.frame);
}
}
return rowHeight;
}
A 特点 1,cell 中控件渲染的同时,在计算
一个 cell, 下面的图片展示相关
五种布局:
没有图片
单张
二三张
4 张
5 张及以上
for (UIButton *btn in self.contentImageBtns) {
[btn removeFromSuperview];
}
[self.contentImageBtns removeAllObjects];
if (timeLineModel.contentImages.count == 0) {
[self.seperatorView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self);
make.right.equalTo(self);
make.height.equalTo(@15);
make.top.equalTo(self.expandBtn.mas_bottom).offset(10);
}];
} else if (timeLineModel.contentImages.count == 1) {
UIButton *contentImageBtn = [[UIButton alloc] init];
[self.contentView addSubview:contentImageBtn];
[contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(11);
make.top.equalTo(self.expandBtn.mas_bottom).offset(10);
make.width.equalTo(@250);
make.height.equalTo(@150);
}];
[self.contentImageBtns addObject:contentImageBtn];
} else if (timeLineModel.contentImages.count == 2 || timeLineModel.contentImages.count == 3) {
for (int i = 0; i < timeLineModel.contentImages.count; i++) {
UIButton *contentImageBtn = [[UIButton alloc] init];
[self.contentView addSubview:contentImageBtn];
[contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(11 + i * (10 + 90));
make.top.equalTo(self.expandBtn.mas_bottom).offset(10);
make.width.equalTo(@90);
make.height.equalTo(@90);
}];
[self.contentImageBtns addObject:contentImageBtn];
}
} else if (timeLineModel.contentImages.count == 4) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
UIButton *contentImageBtn = [[UIButton alloc] init];
[self.contentView addSubview:contentImageBtn];
[contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(11 + j * (10 + 90));
make.top.equalTo(self.expandBtn.mas_bottom).offset(10 + i * (10 + 90));
make.width.equalTo(@90);
make.height.equalTo(@90);
}];
[self.contentImageBtns addObject:contentImageBtn];
}
}
} else if (timeLineModel.contentImages.count == 5 || timeLineModel.contentImages.count == 6 || timeLineModel.contentImages.count == 7 || timeLineModel.contentImages.count == 8 || timeLineModel.contentImages.count == 9) {
for (int i = 0; i < timeLineModel.contentImages.count; i++) {
UIButton *contentImageBtn = [[UIButton alloc] init];
[self.contentView addSubview:contentImageBtn];
[contentImageBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self).offset(11 + (i % 3) * (10 + 90));
make.top.equalTo(self.expandBtn.mas_bottom).offset(10 + (i / 3) * (10 + 90));
make.width.equalTo(@90);
make.height.equalTo(@90);
}];
[self.contentImageBtns addObject:contentImageBtn];
}
}
改进关键点是,手动计算 Frame , 停止使用 Auto layout
( Auto layout ,也会被计算为控件的 Frame )
还有,开辟线程计算富文本,与缓存高度
- 修改后,B FPS 从 37 到 58。 进步是巨大的
B 改进 0,将布局信息的计算,与渲染分离
将布局信息的计算,放在子线程,
控件的渲染,放在主线程
让主线程保持,相对的轻松
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 子线程处理数据
NSData *data = // ...
NSDictionary *dicJson=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (id json in dicJson[@"data"]) {
TimeLineModel *timeLineModel = [TimeLineModel yy_modelWithJSON:json];
[self.timeLineModels addObject:timeLineModel];
}
for (TimeLineModel *timeLineModel in self.timeLineModels) {
TimeLineCellLayout *cellLayout = [[TimeLineCellLayout alloc] initWithModel:timeLineModel];
[self.layouts addObject:cellLayout];
}
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程,显示数据
[self.timeLineTableView reloadData];
});
});
B 改进 1,手动布局,取代自动布局
一个 cell, 下面的图片展示相关
把上面同样功能的自动布局代码,中的布局信息,
抽离出来
self.imageRects = [NSMutableArray array];
if (_timeLineModel.contentImages.count > 0){
if (_timeLineModel.contentImages.count == 1) {
CGRect imageRect = CGRectMake(11, CGRectGetMaxY(self.expandRect) + 10, 250, 150);
[self.imageRects addObject:@(imageRect)];
} else if (_timeLineModel.contentImages.count == 2 || _timeLineModel.contentImages.count == 3) {
for (int i = 0; i < _timeLineModel.contentImages.count; i++) {
CGRect imageRect = CGRectMake(11 + i * (10 + 90), CGRectGetMaxY(self.expandRect) + 10, 90, 90);
[self.imageRects addObject:@(imageRect)];
}
} else if (_timeLineModel.contentImages.count == 4) {
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
CGRect imageRect = CGRectMake(11 + j * (10 + 90), CGRectGetMaxY(self.expandRect) + 10 + i * (10 + 90), 90, 90);
[self.imageRects addObject:@(imageRect)];
}
}
} else if (_timeLineModel.contentImages.count == 5 || _timeLineModel.contentImages.count == 6 || _timeLineModel.contentImages.count == 7 || _timeLineModel.contentImages.count == 8 || _timeLineModel.contentImages.count == 9) {
for (int i = 0; i < _timeLineModel.contentImages.count; i++) {
CGRect imageRect = CGRectMake(11 + (i % 3) * (10 + 90), CGRectGetMaxY(self.expandRect) + 10 + (i / 3) * (10 + 90), 90, 90);
[self.imageRects addObject:@(imageRect)];
}
}
}
B 改进 2, 缓存高度
两个层面,
首先,缓存了 cell 的高度
其次,缓存了 cell 中,各个控件的位置信息 frame
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.layouts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TimeLineCell *cell = [tableView dequeueReusableCellWithIdentifier:ResuseID];
[cell configureLayout:self.layouts[indexPath.row]];
return cell;
}
* 图片,也挺重要的
网络下载图片数据,到手机屏幕上可视的位图,
有一个解码的过程
这个,挺消耗性能。应该放在子线程中,处理
SDWebImage 帮你做了
此时,还用到了自动释放池
@autoreleasepool{ }
总结,常用的实践,就是
对于复杂的界面,我们拿到网络请求到的数据后,
一般不会直接到主线程,一边计算,一边刷新 UI
- 而是,开个线程,
在开辟的线程里面,计算出 UI 需要的信息
( 就是在这个子线程,把能做的,全部做掉 )
最后,回到主线程,刷新 UI
区别是,主线程操作的事件,明显轻松了很多
缺点是,开发代码,有增长。
程序性能,与开发效率,存在一个平衡