欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OC底层探索(二十六)界面优化

程序员文章站 2024-03-24 14:21:34
...

界面卡顿的原理

由于iOS的渲染机制,会造成掉帧的情况,所以界面会卡顿。请参考文章浅谈图像撕裂问题及解决方法,此文章中就不过多的解释界面卡顿的原理了。

卡顿检测

1. YYKit库

YYKit下载地址

YYkit主要是使用CADisplayLink 绑定到runloop上进行监听渲染次数。

2. 监听runloop

监听runloop事物处理的时间,主要监听runloop的kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting这两个状态值,如果监听到了超过两秒,说明页面产生了卡顿

微信检测工具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图片的解压缩)

SDWebImage

3. 按需加载

  • 异步渲染内容到图片。

  • 按照滑动速度按需加载内容。

VVeboTableViewDemo:缺点是会有空白。

4. 异步渲染

view 和 layer的区别

  • view是通过layer驱动的
  • view是可以交互,layer是不可以的
  • view侧重于显示,layer是对内容的绘制
  • view是layer的代理,view的显示是交给layer的display进行渲染的。

Graver

  • 异步绘制,就是可以在子线程把需要绘制的图形,提前在子线程处理好。将准备好的图像数据直接返给主线程使用,这样可以降低主线程的压力。

  • Graver将某个模块绘制成了一张位图,从而优化了界面

  • 由于整个模块是一张位图,事件的响应没有了,所以需要重写UIView的响应事件。

异步渲染流程:
OC底层探索(二十六)界面优化

相关标签: IOS 界面优化