iOS性能优化』--UITableView

文章来源

iOS性能优化』--UITableView

优化方案

一、善用重用标识
二、设置预估行高,预先缓存动态行高
三、减少SubViews层级、异步绘制、避免离屏渲染、使用Hidden隐藏图层
四、分屏加载数据,预先异步请求数据
五、滑动TableView时,按需加载内容
六、cell类中应该避免请求网络加载数据
七、在willDisplayCell:forRowAtIndexPath:代理方法中的绑定数据


一、善用重用标识

这个属于基础知识范畴,就不再过度的讲解了。
只需要了解使用static修饰重用标识名称能够保证这个标识只会创建一次,提高性能。接着就是调用dequeueReusableCellWithIdentifier:方法获取缓存池中的Cell。如果没有就调用initWithStyle:ReusIdentifier:方法创建一个新的Cell。注意事先需要调用registerNib/registerClass方法为TableView注册一下标识。


二、设置预估行高,预先缓存动态行高

1. 设置预估行高

我们知道UITableView是通过UITableView代理方法heightForRowAtIndexPath:方法来设置行高。自从iOS8.0之后,苹果新增了self-sizing cell的概念,也是cell可以自己计算行高,使用需要满足是三个条件:

(1) 使用Autolayout进行UI布局约束
(2) 指定TableViewestimatedRowHeight属性的默认值
(3) 指定TableViewrowHeight的属性为UITableViewAutomaticDimension

TableView在加载数据时会先通过estimatedRowHeight:AtIndexPath处理全部数据,此时我们只需要提供一个粗略的高度,待到cell对象创建之后再去设置cell的真实高度。而且只会处理当前屏幕范围内的cell,这样子会显著的提升加载的性能。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {  
    return 50.0;  
}  

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {  
    return 30.0;  
}  

2.预先计算并缓存行高

自从iOS8.0之后,TableView的数据源的调整时序也发生了变化,下图左边为iOS7.0以及之前的时序,右边为iOS8.0以及之后的时序:

image

从上图可以很容易的分析出,iOS8.0之后在获取cell对象之后会再次调用heightForRowAtIndexPath:方法获取行高,这也就意味着我们其实可以先创建cell对象,之后再提供行高。具体方法我们可以在cell类中添加layoutAttribute属性,记录相应的UIEdgeInsets,然后在设置cell真实高度的时候返回。iOS7.0之前则必须在cell对象处啊给你讲爱你之前先获得所有cell的高度。


三、减少SubViews层级、异步绘制、避免离屏渲染、使用Hidden隐藏图层

1. 减少图层层级数量

当我们自定义某个cell,并在cell上添加大量的系统控件后,在创建该cell对象时系统会调用底层接口进行绘制,大量的添加操作会消耗很大的资源同时会影响渲染的性能。

2. 异步绘制

解决因图层层级多造成的性能问题,我们可以通过drawRect:方法,调用Core Graphics框架中的API进行异步绘制,提高效率。drawRect:本身是移步的。另外drawRect:中大量的绘制也会造成内存的增长,可以使用CAShapeLayer来代替。

3. 减少多于的绘制操作

在实现drawRect:方法的时候,他的rect参数就是我们需要绘制的区域,在rect范围之外的区域不要绘制,否则会消耗相当大的资源。

4. 图片加载时机选择

首先在cell中添加图片应该尽量避免使用imageWithNamed:方法,因为该方法会将图片缓存到内存中。而且应该使用imageWithContentsOfFile:方法来替换,该方法在图片使用完后系统会自动释放资源,并不会缓存下来。另外结合SDWebImage框架的使用可以显著提高图片加载的性能。

5. 避免动态添加图层

在cell中应该尽量避免动态创建图层。在初始化cell的时候一并将所有的图层预先创建好,通过hidden属性控制子图层的显示或隐藏,因为单纯的显示操作要比创建快得多。

6. 避免离屏渲染

什么是离屏渲染?我们知道iOS底层的渲染框架使用的是OpenGL ESOpenGL中,GPU渲染屏幕方式有两种:当前屏幕渲染On-Screen Rendering)和离屏渲染Off-Screen Rendering)。它们的区别是当前屏幕渲染操作是在当前屏幕缓冲区完成,而离屏渲染会在另外一个新开辟的缓冲区完成渲染操作,开启离屏渲染的代价就是需要新开辟一块新的缓冲区,在渲染的过程中还会多次切换上下文,这些都是很消耗性能的。

  • 为图层设置遮罩(layer.mask
  • 设置图层的layer.masksToBounds/view.clipsToBounds属性为True
  • 设置图层的layer.allowsGroupOpacity的属性为Truelayer.opacity小于1.0
  • 设置图层阴影(layer.shadow
  • 设置图层的layer.shouldRasterize的属性为True
  • 具有layer.cornerRadiuslayer.edgeAntialiasingMasklayer.allowsAntialiasing的图层
  • 文本(任何种类,包括UILabelCATextLayerCore Text等)
  • 使用CGContextdrawRect:方法中绘制
    上述情况均会造成离屏渲染。
7. 图片圆角优化

使用贝塞尔曲线 + Core Graphics框架设置圆角

- (void)setImageCircularEdge:(UIImageView *)imageView {  

    //开始对imageView进行画图  
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);  
    //使用贝塞尔曲线画出一个圆形图  
    [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];  
    [imageView drawRect:imageView.bounds];  
    imageView.image = UIGraphicsGetImageFromCurrentImageContext();  
    //结束画图  
    UIGraphicsEndImageContext();  
}  

使用贝塞尔曲线 + CAShapeLayer设置圆角

- (void)setImageCircularEdge2:(UIImageView *)imageView {  

    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];  
    CAShapeLayer *maskLayer=[[CAShapeLayer alloc] init];  
    //设置大小  
    maskLayer.frame = imageView.bounds;  
    //设置图形样子  
    maskLayer.path = maskPath.CGPath;  
    imageView.layer.mask = maskLayer;  
}  

8. 图片阴影优化
- (void)setImageShadow:(UIImageView *)imageView {  

    imageView.layer.shadowColor = [UIColor grayColor].CGColor;  
    imageView.layer.shadowOpacity = 1.0;  
    imageView.layer.shadowRadius = 2.0;  
    UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.frame];  
    imageView.layer.shadowPath = path.CGPath;  
}  


四、分屏加载数据,预先异步请求数据

在我们的项目开发中列表视图的应用很多,有时数据比较多的时候我们不可能一次加载所有数据,这样子会导致内存的暴涨,同时用户不一定会浏览所有的信息,造成资源浪费。这时我们可以通过分屏加载来解决这个问题,比如第一次加载10条数据,当我向上滑动列表的时候通常我们会再次去请求数据接口获取下一个10条数据。这个时候如果我们不做任何的处理,那么我会发现每次划过10条数据的时候列表都需要停顿一下,等待数据加载。这样子我们的列表就表现的不是很流畅了,那么怎么解决这个问题呢?

提前异步预加载数据!第一次加载完10条数据之后可以再预先加载下10条数据,当划过第10条数据时,再请求下10条数据。这样子我们的列表就表现的很流畅了。


五、滑动TableView时,按需加载内容

有些情况下我们可能会去快速的滑动列表,这时候其实会有大量的cell对象被创建、被重用,其实我们可能只是去浏览列表停止的那一页的上下一定范围内的信息,前面快速划过的那些信息对我们来说都是无用的。有什么方法让我们只去加载最后那页的目标范围内的列表数据呢?那就是通过ScrollView的代理方法'scrollViewWillEndDragging:withVelocity:targetContentoffset:'来实现的。

#pragma mark - UIScrollViewDelegate  
//按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。  
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {  

    NSIndexPath *targetPath = [_myTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];  
    NSIndexPath *firstVisiblePath = [[_myTableView indexPathsForVisibleRows] firstObject];  
    NSInteger skipCount = 8;  
    if (labs(firstVisiblePath.row - targetPath.row)>  skipCount) {  
        NSArray *temp = [_myTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, _myTableView.frame.size.width, _myTableView.frame.size.height)];  
        NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];  
        if (velocity.y<0) {  
            NSIndexPath *indexPath = [temp lastObject];  
            if (indexPath.row+33) {  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];  
            }  
        }  
        [_dataList addObjectsFromArray:arr];  
    }  
}  

targetContentOffsetTableView减速到停止的地方,velocity表示速度向量。


六、cell类中应该避免请求网络加载数据

如果确实有需求不可避免,可以将网络加载任务添加到Runloop中,设置DefaultRunloopModule模式。这样子可以起到延迟加载的作用。


七、在willDisplayCell:forRowAtIndexPath:代理方法中的绑定数据

初学iOS的时候,各类教程以及书籍中都喜欢在cellForRowAtIndexPath:方法中绑定数据,然后此时的cell其实还未显示,该方法中包含了大量的布局、绘制相关的操作。我们应该在该方法中尽量简化我们自身的逻辑操作。这时我们可以使用在willDisplayCell:forRowAtIndePath:方法中绑定数据。

#pragma mark - UITableViewDataSource  
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
    static NSString *cellIdentifier = @"MyTableViewCell";  
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];  
    if (!cell) {  
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];  
    }  

    return cell;  
}  

#pragma mark - UITableViewDelegate  
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {  

    NSDictionary *dict = self.dataList[indexPath];  
    [cell updateData:dict];  
}  

性能优化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容