OC底层探索(二十六)界面优化
程序员文章站
2024-03-24 14:21:34
...
界面卡顿的原理
由于iOS的渲染机制,会造成掉帧的情况,所以界面会卡顿。请参考文章浅谈图像撕裂问题及解决方法,此文章中就不过多的解释界面卡顿的原理了。
卡顿检测
1. YYKit库
YYkit主要是使用
CADisplayLink
绑定到runloop
上进行监听渲染次数。
2. 监听runloop
监听
runloop
事物处理的时间,主要监听runloop的kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
这两个状态值,如果监听到了超过两秒
,说明页面产生了卡顿
。
微信检测工具matrix:就是利用了此原理。
3. ping runloop
创建一个轮询,进行询问runloop是否在忙碌状态
滴滴检测工具 DoraemonKit
界面优化方案
1. 预排版
-
思路分析:开辟两个线程
- 网络请求的线程: 进行异步网络请求
- 预排版线程:提前计算好布局属性,比如根据内容动态设置高度、宽度等。
-
案例:
lineCellLayout
static const CGFloat nameLeftSpaceToHeadIcon = 10;
static const CGFloat titleFont = 15;
static const CGFloat msgFont = 15;
static const CGFloat msgExpandLimitHeight = 140;
static const CGFloat timeAndLocationFont = 13;
- (instancetype)initWithModel:(LGTimeLineModel *)timeLineModel{
if (!timeLineModel) return nil;
self = [super init];
if (self) {
_timeLineModel = timeLineModel;
[self layout];
}
return self;
}
- (void)setTimeLineModel:(LGTimeLineModel *)timeLineModel{
_timeLineModel = timeLineModel;
[self layout];
}
- (void)layout{
CGFloat sWidth = [UIScreen mainScreen].bounds.size.width;
self.iconRect = CGRectMake(10, 10, 45, 45);
CGFloat nameWidth = [self calcWidthWithTitle:_timeLineModel.name font:titleFont];
CGFloat nameHeight = [self calcLabelHeight:_timeLineModel.name fontSize:titleFont width:nameWidth];
self.nameRect = CGRectMake(CGRectGetMaxX(self.iconRect) + nameLeftSpaceToHeadIcon, 17, nameWidth, nameHeight);
CGFloat msgWidth = sWidth - 10 - 16;
CGFloat msgHeight = 0;
//文本信息高度计算
NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:5];
NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:msgFont],
NSForegroundColorAttributeName: [UIColor colorWithRed:26/255.0 green:26/255.0 blue:26/255.0 alpha:1]
,NSParagraphStyleAttributeName: paragraphStyle
,NSKernAttributeName:@0
};
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:_timeLineModel.msgContent attributes:attributes];
msgHeight = [self caculateAttributeLabelHeightWithString:attrStr width:msgWidth];
if (attrStr.length > msgExpandLimitHeight) {
if (_timeLineModel.isExpand) {
self.contentRect = CGRectMake(10, CGRectGetMaxY(self.iconRect) + 10, msgWidth, msgHeight);
} else {
attrStr = [[NSMutableAttributedString alloc] initWithString:[_timeLineModel.msgContent substringToIndex:msgExpandLimitHeight] attributes:attributes];
msgHeight = [self caculateAttributeLabelHeightWithString:attrStr width:msgWidth];
self.contentRect = CGRectMake(10, CGRectGetMaxY(self.iconRect) + 10, msgWidth, msgHeight);
}
} else {
self.contentRect = CGRectMake(10, CGRectGetMaxY(self.iconRect) + 10, msgWidth, msgHeight);
}
if (attrStr.length < msgExpandLimitHeight) {
self.expandHidden = YES;
self.expandRect = CGRectMake(10, CGRectGetMaxY(self.contentRect) - 20, 30, 20);
} else {
self.expandHidden = NO;
self.expandRect = CGRectMake(10, CGRectGetMaxY(self.contentRect) + 10, 30, 20);
}
CGFloat timeWidth = [self calcWidthWithTitle:_timeLineModel.time font:timeAndLocationFont];
CGFloat timeHeight = [self calcLabelHeight:_timeLineModel.time fontSize:timeAndLocationFont width:timeWidth];
self.imageRects = [NSMutableArray array];
if (_timeLineModel.contentImages.count == 0) {
// self.timeRect = CGRectMake(10, CGRectGetMaxY(self.expandRect) + 10, timeWidth, timeHeight);
} else {
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)];
}
}
}
if (self.imageRects.count > 0) {
CGRect lastRect = [self.imageRects[self.imageRects.count - 1] CGRectValue];
self.seperatorViewRect = CGRectMake(0, CGRectGetMaxY(lastRect) + 10, sWidth, 15);
}
self.height = CGRectGetMaxY(self.seperatorViewRect);
}
viewController
//异步加载
- (void)loadData{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//根据网络请求而来的json 创建timeLineModels
...
[self.layouts addObject:cellLayout];
...
dispatch_async(dispatch_get_main_queue(), ^{
[self.timeLineTableView reloadData];
});
}
}
// timeLineTableView的逻辑
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.layouts[indexPath.row].height;
}
#pragma mark -- UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.layouts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
LGTimeLineCell *cell = [tableView dequeueReusableCellWithIdentifier:ResuseID];
[cell configureLayout:self.layouts[indexPath.row]];
cell.expandBlock = ^(BOOL isExpand) {
LGTimeLineModel *timeLineModel = self.layouts[indexPath.row].timeLineModel;
timeLineModel.expand = !isExpand;
self.layouts[indexPath.row].timeLineModel = timeLineModel;
[tableView reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
};
return cell;
}
2. 预解码 & 预渲染
以UIImage为例,image是的赋值是先通过解压缩 -> 渲染 -> 显示
,一般在UIImageVIew.image = image
都是在主线程操作,造成一些界面的耗时,预解码和预渲染在异步线程(iOS图片的解压缩)
3. 按需加载
-
异步渲染内容到图片。
-
按照滑动速度按需加载内容。
VVeboTableViewDemo:缺点是会有空白。
4. 异步渲染
view 和 layer的区别
- view是通过layer驱动的
- view是可以交互,layer是不可以的
- view侧重于显示,layer是对内容的绘制
- view是layer的代理,view的显示是交给layer的display进行渲染的。
-
异步绘制,就是可以在子线程把需要绘制的图形,提前在子线程处理好。将准备好的图像数据直接返给主线程使用,这样可以降低主线程的压力。
-
Graver将某个模块绘制成了一张位图,从而优化了界面
-
由于整个模块是一张位图,事件的响应没有了,所以需要重写UIView的响应事件。
异步渲染流程:
下一篇: 74. 搜索二维矩阵