优化UITableViewCell高度计算Swift版

最近在想着怎么提高自己的iOS技能,不断的阅读别人的blog。发现读完了不练手很快又忘掉了,练手吧又不知道从何处下手。所以就想着看看别人写的库是怎么实现的,体量比较大的看起来很费劲,影响士气。那就从体量比较小的看起。

UITableView应该是iOS非常常用的一个组件,学过iOS都会用,但是用好它却并不是那么easy 。不恰当的使用往往会造成掉帧引起界面卡顿。

关于界面上图像的显示原理,卡顿造成的原因之类的可以看这篇,iOS 保持界面流畅的技巧 , YY大神是用异步渲染的技术使界面流程,目前还看不懂。。,但是那些成像和卡顿的原理还是值得好好看看的。

如果你是因为圆角阴影太多等问题引起的,可以看iOS 高效添加圆角效果实战讲解

上面都不是重点,重点是由于系统会多次重复计算TableviewCell的高度

关于UITableViewCell怎样设置高度以及对性能影响的详细描述请直接戳 优化UITableViewCell高度计算的那些事

上面那个文章一定要读,是百度大神sunnyxx 写的(虽然现在已经离开百度了)。大神详细讲述了UITableViewCell计算的各种方式以及会有哪些性能问题,并且写了一个开源库解决了问题,提高了UITableView滑动的流畅性。开源库地址:UITableView+FDTemplateLayoutCell,作者写那篇文章的时候那个库的版本是1.2,现在已经1.5了。

本文带大家详细探索那篇文章所对应的库(1.2版)。并实现一个swift版本的库。(选择他主要是因为简短,也尝试去看过YY大神的库,各种看不懂最后只好先搁置了。。)

有些人会问,别人已经有了为啥还要再实现一遍,OC和Swift不是可以桥接么直接用就行了。我想说用swift实现主要是想理解作者的每行代码的含义,作者的思想,以及对应swift中的实现方式。(OC和swift还是有挺多区别的),理解了库的每行代码,自己使用也放心,自己实现了一个以后有什么别的需求自己直接可以改自己这版代码,掌握了他的思想,可以类比写出别的比较好用的库。(一举多得 还不好好看源码?)

这个库能干啥 ,就是利用缓存tableviewcell的高度提高滑动的流畅性,我用了 sunnyxx 原有的数据,添加了很多emoj表情在自己实现的swift版上做了测试 。(不加很多emoj,简短的文字缓存和不缓存基本都没秒60帧没啥区别)

结果是缓存的基本在每秒50多帧 ,没缓存的会掉到二十几甚至十几帧 。(
YY大神的异步渲染估计可以保持在60帧 )

下面放两副截图:(测试必须在真机上运行,模拟器上没有GPU,是CPU模拟的 都会掉帧看不到效果)

没有缓存拖动过程截图

缓存后拖动过程截图

那个FPS是自己写的一个view利用CADisplayLink 实现的,感兴趣的可以看源码,模仿YY大神库中的一个类。

界面绘制大量emoj的时候一般都会有卡顿,尤其适用autolayout 每次计算,我们缓存了cell高度后可以缓解这样的问题。

下面来进入正题,看下实现思路,其实如果你看了这篇文章,你已经知道了实现思路优化UITableViewCell高度计算的那些事

主要是利用Runloop在空闲状态下后台计算tableviewcell的高度并缓存起来。然后在使用的时候就直接从缓存中去,这里都放在一个数组里存在内存。

对Runloop以及几个mode不懂的可以看sunnyxx blog中的视频 视频可戳 , 文章的话可以看看 深入理解RunLoop【iOS程序启动与运转】- RunLoop个人小结

如果你耐着性子把上面的连接都看了,你再看sunnyxx blog中的解释应该就不会晕了。其实就是在kCFRunLoopDefaultMode模式下BeforWaitting状态去执行计算的。

下面来探究源码。首先在UITableView+FDTemplateLayoutCell 下载源码,下载1.2版本。 1.5版本没有用Runloop实现。

然后你得到的库就两个文件 (在swift中就只有一个啦)。

配图

大概看了下,.m文件只有500行。

PS:只要跟着看,肯定可以看懂,后面还有很多干货

看下作者的实现思路:(从上往下看)
1、 创建了一个_FDTemplateLayoutCellHeightCache类,就是管理Cache的一个类,里面有两个属性四个方法。

属性:
  • sections 这个变量就是用来存储缓存的height的一个二维数组。(因为tableview有section和row组成所以必须二维)
  • _FDTemplateLayoutCellHeightCacheAbsentValue 这个是一个静态常量,就是用来标记没有缓存高度row 。
方法:
  • buildHeightCachesAtIndexPathsIfNeeded:indexPaths
    这个方法传入indexPaths数组来给sections中还没有初始化的元素进行初始化
  • hasCachedHeightAtIndexPath:indexPath 根据下标索引判断是否有缓存(其实就是判断是否等于上面那个静态常量)
  • cacheHeight:height:byIndexPath 根据indexPath给sections赋值。
  • cachedHeightAtIndexPath:indexPath 根据indexPath取值

这个类主要操作和存储缓存的。看看swift中的实现方法。

比他多写了个copyWithZone: 由于要赋值需要实现NSCopying协议。

接着往下看,他写了个UITableView的扩展

配图

看第一个方法

配图

这个方法主要是通过你传入的一个identifier(就是复用的id)获取cell

第一句是这样的

OC中的 _cmd 代表的就是本方法,objc_getAssociatedObject 获取一个关联对象的属性,其实在swift中 我们可以直接给系统已有的类添加属性

比如这个方法中的cell还有个属性 fd_isTemplateLayoutCell 我们实现一个swift版的

参考这篇文章 Swift & the Objective-C Runtime
更多Runtime知识扩展阅读:Objective-C Runtime
Objective-C Runtime 1小时入门教程

懂了这些东西,我们就可以把他所有用_cmd和OC版本蹩脚的属性加上去,用我们比较优美的方式。

然后那个通过id获取cell的方法在swift中的实现就没那么长了,很简短的。

我们在使用这个库的时候回传入identifier , 库里面的通过这个方法来获取cell。

然后他提供了一个方法用来获取上面管理Cache的_FDTemplateLayoutCellHeightCache的对象 。
fd_cellHeightCache

由于我们给他也添加了属性,所以我们这个方法也异常的简单

接下来就是他写的几个属性,我们上面用优美的方式写过了,可以无视

下面又是一个分类,(这个是重点计算高度,调用缓存管理方法的分类)

配图

这个里面的方法在他blog中也有提到就是在NSDefaultRunLoopMode下当状态将要进入休眠的时候把计算方法分解成多个RunLoop Source任务(source0)

这个方法将创建一个 Source 0 任务,分发到指定线程的 RunLoop 中,在给定的 Mode 下执行,若指定的 RunLoop 处于休眠状态,则唤醒它处理事件.

下面来看看swift中的实现;

注释很清楚,主要逻辑就是先通过遍历所有section和row找到还没有缓存的row,然后加入到待缓存数组 ,创建一个observer去监听Runloop的状态 ,如果空闲了去创建source0任务,执行计算方法并缓存起来。如果预缓存任务完成了就把监听的Observer移除了。(说的挺清楚了,有啥问题可以讨论)

下面又是一个扩展

配图

这个分类看起来比较长,其实也是个纸老虎。(大多数都是同理的代码)

因为我们会有一些操作导致cell的改变,所以这里作者要保证在每次cell改变的时候把sections数组改掉,然后如果新增或者修改了 需要重新计算高度。用到了methodSwizzle 黑魔法,索性我前面提到的blog也给出了swift中方法替换的实现方法。

这里作者把swizzle放在了UITableView的load类方法中,在swift中我们需要重写initialize

所以这里的实现方式如下:

把所有可能改变indexPath的方法抓出来 然后遍历替换掉 。 下面就写上自己的方法

这里就放了三分方法例子,其他的看源码,地址在文尾。

貌似一切都准备的差不多了,下面就是留给外界使用者调用的方法了

第一个方法是手动计算然后缓存,第二个方法是先从缓存中取,如果没有再调手动计算的方法。

然后外界使用的时候只需要这样就已经给tableview计算height加上了缓存

总算读完了一个库,虽然小,但是是一个好的开端。

看源码,看源码。地址:github地址

有什么问题在底下交流。效果图上面已经放出,大家可以下载github上的源码,在真机上运行,如果swift项目中有想使用的只需要把下图中文件copy到你的项目中就可以了

配图
1 4 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部