iOS系列开发-UITableView性能优化

#iOS系列开发-UITableView性能优化ios

在咱们的平常开发中,不少开发人员最常接触的就是UITableView或者UICollectionView来布局某些列表等界面. 这里咱们就拿UITableView来做为说明内容 绝大部分的时候,一个UITableView的内容不会不少,cell的样式\高度也不会不少元化,其仅仅做为一个展现用的UITableView来讲,不少时候其性能都是很不错的. 可是也会有小众的时候,一个做为列表展现的界面会有不少不少数据,并且是实时的会加载不少新的内容,表格的样式也不惟一,有的仅有文字,有的仅有图片,有的高度很长,在复杂点的不少cell虽然整体上差很少,可是却会有不少或多或少的布局上的不同或者组件上的差距git

固然,并非说有了这些复杂的内容,咱们的UITableView就会性能变差,可是咱们却能够说性能上比较差的UITableView,不少缘由都是由于这些不定的因素,复杂的逻辑判断,复杂的数据处理,复杂的图形渲染,复杂的高度计算等等致使的.算法

并且若是表格的性能真的已经变差了,那么其调优的步骤是必定须要进行的,不然做为开发人员,咱们过不了本身的关,过不了测试的关,过不了产品的关...json

可是说白了UITableView也就那么些方法 咱们可以利用的有哪些?数组

@property (nonatomic) CGFloat rowHeight;
复制代码

是的,你会发现当rowHeight时惟一固定的时候,每每性能不会太差缓存

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
复制代码

咱们都知道,当咱们使用了代理的方法以后,上面的行高属性就会失效了,可是相对应的,咱们须要给每一行都返回一个行高,这个是没法避免的,那么咱们可否在这里作些优化呢? 答案是确定的,并且是显著的. 为何这么说呢? 当咱们的UITableView是一个动态行高的表格的话,咱们以往的算法就是动态计算,什么意思呢? 大体是这样的性能优化

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    DetaileModel *model = _listArray[indexPath.row];//取出模型
    return [DetailCell heigthForModel:model];//计算所需高度并返回
}
复制代码

可是响应的,由于内容的多样化,可能有,这部分的计算本事虽然不会很耗时,可是由于咱们reloadData的时候或者滚动的时候,其都须要不停的计算高度并返回,这样就会形成不少的展现表格或者建立或者重用表格cell的时候都须要耗时在计算高度的这部分上,那么咱们如何调节呢?这部分计算确定是须要的,此时你可能会说,iOS如今支持自适应高度,bash

@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
复制代码

是的,UITableView中,咱们能够写一个预估行高,而后使用xib或者storyboard或者手动layout的时候编写好约束的时候,便可动态自动的返回行高,但这样的性能其实更差,时间所有都在调节约束上面了,而后硬生生的依靠约束来调节行高,这样的性能只会更差,不推荐.网络

因此咱们能够采起的是app

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    DetaileModel *model = _listArray[indexPath.row];//取出模型
    return model.cellHeight;//得到所需高度并返回
}
复制代码

若是咱们把cellHeight当作一个属性,提早缓存好,以后直接获取怎么样呢? 此时你就会说,这样有什么用吗? 简单的说几点 1.使用内存换性能 2.不须要重复计算

为何这么说呢? 首先咱们会发现,每每咱们计算行高和设置cell的时候都是根据模型数据来判断哪些须要显示哪些不须要显示,显示的话显示的位置和大小分别是多少,咱们都是根据数据模型的内容来判断的.那么咱们若是这设置这个模型数据的时候(网络请求回来json转模型的时候),咱们就根据这个模型的数据添加一个cellHeight的属性,而且直接计算好保存在模型中,当作模型的一个字段,那么咱们在reloadData的时候咱们就会发现咱们把计算高度的事情提早一步作好了,咱们只须要返回model的cellHeight便可.并且由于其存储在该模型数据中,那么其跟模型同样,一直是存在的,咱们便可以在从新刷新表格或者滚动视图的时候都不须要再一次计算了(只要改模型存在,那么该cellHeight属性就一直存在).

咱们会发现仅仅是一个提早计算并存储起来,一个表格的性能就会大大提升,一些稍微不是特别复杂的表格此时已经就可以很流畅了.

至此咱们会发现咱们使用了相似设置cell的方式

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellid = @"cellid";
    DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];
    if (!cell) {
        cell = [[DetailCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];
    }
    cell.model = _listArray[indexPath.row];
    return cell;
}
复制代码

不少的开发者都使用相似的方式,在cell中有一个model属性,或者设置model的方法,以此来经过传递一个model来设置数据,可能在自定义的设置方法中,可能在model的setter方法里面,此时咱们又会一到一个新的问题,某些model自己并不深很复杂,可是咱们须要对这些数据作一些处理以后在作展现,好比图文混排,好比拼接分割转换等处理字符的显示,这些在以往的时候,咱们都是放在设置cell的数据源方法里面,经过模型的传递过来,而后经过进一步的转化或者修改来决定如何显示数据. 可是一样的,此时,咱们就须要不停的在表格更新显示的时候来计算.

此时你确定会想到,处理方法和上面同样,提早计算好! 是的,咱们在设置模型的时候就提早计算好,在模型中添加新的字段,存储这些须要计算的数据,在cell展现的时候直接拿过来用便可.

可是问题来了,新的数据模型仿佛不是数据模型了,参与了不少的计算(内容计算,行高计算...) 那么咱们能不能优化一下呢? 这是咱们以前的逻辑

这里写图片描述
这是咱们如今的逻辑
这里写图片描述
咱们可以轻松的优化了红色部分的性能了 缘由是咱们把全部的须要提早计算的东西都提早了,将原来的须要计算的部分所有转化成了一个内存,使用内存来代替以前的计算,在须要的时候只须要从内存中直接拿出来使用便可.优势上面简单的说明了,只须要在赋值的时候计算好便可,以后就不须要计算了,之后的刷新都是直接从内存中取出所须要的内容或者行高便可 可是,相对应的,模型就变的不在像模型了,(此时的模型中有了计算行高的部分,有了计算新的内容的部分),而且跟网络返回回来的模型已经不是一样的一个模型数据了.比原来有了更多的字段,或许在之后的时候中会有或多或少的问题,好比字段重复殊不知道,好比模型传递,传递屡次以后,已经不知道哪部分是后来本身添加的属性了...

那么咱们使用新的数组来替代呢?咱们使用一个新的数组来替代原来的模型数组 使用一个自建的模型类来代替原来的模型类,里面只有须要的内容,好比行高,好比全部须要显示的内容,这样的一个模型类来组成新的模型数组. 而后UITableView只须要处理新的模型数组来呈现便可. 可是这样的的数组你会发现,丢失了原来的模型.好比咱们点击某一个cell的时候咱们须要将模型传递到下一个界面,此时拿到的数组就不是该模型了,而是咱们本身定义的一个新的模型,这样会不会就不太方便呢?

那么咱们进一步扩展,咱们建立一个DetailModelManager的类,这个类里面有这样的一个属性,model, 咱们在其set方法中作几件事,第一件,就是普通的set,第二件是计算好一个cellHeight并保存为DetailModelManager的属性,而不是model的一个属性,计算好(或者赋值)全部须要显示的内容的字段,并保存为一个个的属性 如今的逻辑变了

这里写图片描述

其实熟悉MVVM框架的同窗此时就确定笑话我了,这个不就是MVVM的VM嘛 是的,这其实就是MVVM的VM部分的一块优点了,如今的列表数组已经变成了DetailModelManager(ViewModel)数组了,全部的须要复杂计算的部分咱们均可以以相似的方式来处理,好比我项目的一个列表

这里写图片描述
此时的列表不是单纯的模型数组 tableView中写法稍变
这里写图片描述
原来全部的model部分转化成viewModel部分 那么viewModel部分作了什么?
这里写图片描述
是的在以前的setModel的方法中我就作了不少事情,把后续须要计算的不少东西都计算结束并保存为属性了
这里写图片描述
至于cell就简单了,原来的model换成viewModel,原来的model的属性,如今变成了viewModel的属性了,并且彻底不须要任何计算.直接都是拿过来使用便可, 后期若是显示的内容不符合需求,只须要修改viewModel的那个字段的计算便可.其余任何地方都不须要修改.

呃,不知不觉说了部分的MVVM的内容,虽然和UITableView的性能优化没有关系,可是毕竟是性能优化的一个实现方式,因此就一并说了一点

  • 总之就是行高必定要缓存

另外,咱们肯能会遇到这样的需求,一个稍微复杂的cell中可能有九宫格展现图片(如新浪微博),可能有滚动视图展现图片(如不少视频类app),这个时候,咱们会发现,用来展现图片的cell展现的内容是动态的,即数量是不定的,因而咱们不少人就都会想到,九宫格啥的或者滚动视图啥的,咱们均可以用coolectionView来实现嘛,彻底动态保证,多好... 可是其实这样作仍然是有不少麻烦的. 首先是层级关系,一个cell中包含collectionView等虽然不会有不少影响,可是这个cell的代码就会变的很庞大,很麻烦.并且动态添加的方式会很消耗性能,缘由上面也差很少说了,咱们上面一直就是在作表格的性能优化,你在cell中又加一个表格,虽然不复杂,可是该遇到的性能问题一个都不会丢的所有触发,并且或许是double. 如何作一点优化呢?首先咱们会发现诸如新浪微博的九宫格图片展现样式其实也是有规律的,其最多展现9张图片.因此咱们其实没有必要使用动态建立的方式,咱们在建立cell的时候就建立9个imageView便可.在使用或者复用的时候,根据图片个数或者展现需求来保证其显示或者隐藏便可.这样的cell你会发如今层次上和你建立collectionview是同样的,可是在展现上,其非动态获取数据源展现和建立cell,其就是简单的设置图片的显示和隐藏便可,而后根据图片的格式,调整好约束就行了.一个简单的处理咱们会发现效果是很明显的,这样能尽量的减小cell建立或从缓存池取时由于布局子控件所消耗的时间. 因此说

  • 全部的子视图都要预先建立
    复制代码
  • 若是不须要显示能够设置hidden
    复制代码

另外咱们是否会发现咱们在开发app的时候,A作push动做到B.若是B没有设置背景色的话,会出现一个明显的卡顿?因此避免颜色致使问题,咱们最好都给子视图设置好背景色.虽然不会有太大的影响,可是仍是有点效果的 此外,栅格化也是咱们能够关注的一点,首先栅格化是设计中的术语,可是用在咱们的开发一样适用,当咱们遇到表格中的cell层级不少的时候,咱们是能够作个栅格化的操做,就是将 cell 中的全部内容,生成一张独立的图像,在屏幕滚动时,只显示图像 设置属性 self.layer.shouldRasterize = YES;虽然代码很简单,可是你会发现滚动的效果会很明显,固然栅格化的同时必须指定分辨率,不然默认使用 1倍的scale 生成图像! 须要设置 self.layer.rasterizationScale = [UIScreen mainScreen].scale; 固然既然有栅格化优化滚动,那么还有操做来优化其余的,好比绘制. 咱们在开发中都会用到SDWebImage等相似的图片异步加载的.来保证每一个cell中的网络图片在加载数据的时候不会占用主线程,从而来保证cell的流畅性,固然,其实咱们不只仅能作到这点,咱们还能够设置cell的异步绘制,若是 cell 比较复杂,能够设置cell图层的属性 self.layer.drawsAsynchronously = YES;代码一样简单的能够忽略,可是效果也是显著的. 因此

  • 异步加载是很须要的

因此总的来讲咱们在开发表格的时候,咱们须要关注的其实无非也就是简单的几点

  1. 对行高的缓存------不要频繁的动态计算行高,少使用系统自动预估和动态行高,主动负担cell的布局,而不是依靠系统自动'撑开',在这块处理上咱们可使用内存换计算的方式,把内存在模型构建完成的时候就算出来,提早保存好.以后直接使用便可.好比把行高计算放入model、viewmodel的初始化中或者设置数据的方法中,优点是不须要重复计算,减小计算行高时间花销
  2. 子视图提早建立,不要动态建立------对于视图的建立,每一个人都有本身的方式,可是对于cell中来讲,由于cell自己就是一个动态的,因此在对其复用或者建立的时候咱们就要尽可能避免使用动态的方式建立子视图,不管是先removeSubViews再addSubView 亦或者是使用collectionView等动态视图,这些都要在复用的时候避免,若是能够,提早建立好须要的子视图.在须要显示的时候显示,在不须要显示的时候隐藏,这样其建立的耗时工做仅在第一次建立的时候,以后的复用都不会再次发生建立和删除视图等操做,一个简单的隐藏就能够达到咱们的需求,何乐不为?
  3. 图片等展现异步加载------对于这一点,你们都颇有心得.或者说你们都会在开发的时候很直接的作到.若是讲cell中的网络图片的加载使用主线程,那么何止是cell会卡顿,简直都是过小白的操做了.
  4. 尽可能减小cell的层级------子视图的层级不易过多,(不只仅是cell的层级,整个应用的视图中过多)层级若是太多,系统对视图的渲染压力就会相应的变大,毕竟很明显的道理是越少越好蛮.至于如何减小cell的层级,不少方式,好比咱们使用drewRect的方式来绘制文字或者图片,而不使用label等,好比咱们使用UItextField来充当label,这样既能够在某些时候仅展现文字,某些时候用来编辑文字,作到一个控件两种使用方式(在相似须要填写或者选择等信息的界面中我都会这么用),固然若是你对性能要求很高,且技术足够,你能够彻底手工绘制cell,对于此你能够简单的学习setNeedsDisplay
  5. 图层颜色的选择------透明图层对渲染性能会有必定的影响,系统必须将透明图层与下面的视图混合起来计算颜色,并绘制出来。减小透明图层并使用不透明的图层来替代它们,能够极大地提升渲染速度。
  6. 为代理方法瘦身-----自从咱们学会的block的时候咱们会发现咱们在任何地方都习惯使用block的方式来代替代理,在cell的建立中也不例外.好比cell中包含按钮的点击事件,咱们如今都会使用block的方式来传递出来,可是不知不觉中cell的代理方法咱们会发现很庞大,其实这样作是否真的好呢?其实不是,首先庞大的代码就会影响阅读,其次,每个block都会开辟一个空间,因此说你别看只写了一个block,可是相对应的你却付出了block*个数的内存,但其实他们的内容彻底一致,代理之因此存在是有其意义的,一味的使用block来替换代理实际上是不对的,咱们要在合适的时候用合适的方法.cell中的按钮点击,诸如此类的,仍然建议仍是使用代理传递出来.你不会发现性能上的优化,你却能发现代码上的优化.
  7. 减小离屏渲染------离屏渲染,指的是GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。设置了如下属性时,都会触发离屏绘制:shouldRasterize(光栅化)masks(遮罩)shadows(阴影)edge antialiasing(抗锯齿)group opacity(不透明)复杂形状设置圆角等 渐变... ,咱们的cell开发虽然不在,可是不会太过度,咱们用到的比较多的就是几种,如圆角图片,其会形成很明显的离屏渲染,从而形成性能上的变差.因此确定是要对其优化的,固然网络上有不少教你设置圆角图片的贴文,不仿看看
  8. cell栅格化------当shouldRasterize设成true时,layer被渲染成一个bitmap,并缓存起来,等下次使用时不会再从新去渲染了。实现圆角自己就是在作颜色混合(blending),若是每次页面出来时都blending,消耗太大,这时shouldRasterize = yes,下次就只是简单的从渲染引擎的cache里读取那张bitmap,节约系统资源。若是在滚动tableView时,每次都执行圆角设置,确定会阻塞UI,设置这个将会使滑动更加流畅。
  9. 异步加载------不只仅是网络图片的异步加载.drawsAsynchronously属性也是咱们须要关注的.
  10. 性能优化不是惟一的固定的,若是你的性能足够,那么你不优化也行,若是你的性能不够,如何优化,优化哪些都是可选的.非固定的

性能上的优化不只仅如此,不只仅是表格, 在这里推荐一部iOS开发必看书籍,若是融会贯通,你的开发经验足以让别人仰视对待, ios核心动画高级技巧(https://www.gitbook.com/book/zsisme/ios-/details)

此外欢迎访问个人系列博客.最近相对比较忙,不多能抽出时间继续,可是不会断的.

系列:iOS开发-前言+大纲 blog.csdn.net/spicyShrimp…

相关文章
相关标签/搜索