UITableView+FDTemplateLayoutCell 源码探究

  • 在我们日常的业务中,常常伴随大量的UITableView,然而动态地计算Cell的高度常常困扰着我。自从使用了这个组件之后,一切都变得没那么复杂。所以深入学习下这个框架的组件的实现原理。
  • 框架地址:https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

代码文件目录


首先,介绍一下这几个类的基本功能,再层层推进,逐一分析。


关于这个框架,坦白说,从代码中看,作者无疑秀了一波runtime底层的功底,让我这种小白起初一脸懵逼。自然我得换种思路来解读这个框架,那就是从字数最少的类入手吧。

UITableView+FDTemplateLayoutCellDebug

  • 在分类中,如果要声明属性,可以通过使用关联度对象( AssociatedObject ), 通过objc_setAssociatedObject() 添加属性,objc_getAssociatedObject() 获取属性。实际上,相当于在运行时系统中动态地在内存中开辟一块空间,存储debugLogEnabled这个BOOL变量,类似懒加载的方式,通过runtime实现setter & getter方法。
  • 关于runtime的知识点,推荐这篇博客:http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

UITableView+FDKeyedHeightCache

  • 先来看看FDKeyedHeightCache类中声明的属性

不难看出,这是两个指定泛型的可变字典。

  • mutableHeightsByKeyForPortrait : 用于缓存设备竖直放置时,对应key的cell的高度值。
  • mutableHeightsByKeyForLandscape : 用于缓存设备横向放置时,对应key的cell的高度值。

  • FDKeyedHeightCache中的接口方法

  • 这些方法并不晦涩,看到这里,大家不禁会问,self.mutableHeightsByKeyForCurrentOrientation从何而来,这也是我觉得这个类中,细节处理比较好的地方,由于此处考虑到缓存的高度区别了设备方向,所以框架作者,通过一个getter方法来获取对应的存放高度的字典。

  • 根据UIDeviceOrientationIsPortrait()函数,传入当前设备的放置方向([UIDevice currentDevice].orientation

    )进行判断。从而便可以通过属性简洁判断需要从那个字典中取值了。


UITableView+FDIndexPathHeightCache

  • 首先看看FDIndexPathHeightCache中设置的属性

通过前面key的高度缓存分析,不难猜出这几个属性是干什么的。

  • 由于通过NSIndexPath获取高度缓存,NSIndexPath对应section, 以及indexPath。FDIndexPathHeightsBySection这个数组,通过数组嵌套字典的数据结构来存储,不同的section组中对应的cell的高度缓存。

  • FDIndexPathHeightCache中的方法

    由于头文件声明的几个接口方法,与FDKeyedHeightCache中的思路类似,就不再费口舌了,大家翻看源码便一目了然。

  • 这几个封装的方法,主要一点就是通过block来回调,判断删除NSIndexPath对应的cell高度缓存。

  • 在这个类中,最核心的莫过于UITableView (FDIndexPathHeightCacheInvalidation) 这个分类的实现细节,废话少说,继续看代码。

  • 调用的接口方法

  • 这个方法,主要调用的是[self fd_reloadData],看到这里的时候,我们的第一反应应该是此处通过runtime 交换了系统方法的实现。这是一种动态的拦截技巧,也算是基础的runtime知识了,懵逼的小伙伴可以认真阅读下前面提到的关于runtime的大牛博文。

  • 既然如此,先来看看作者重写了哪些系统的方法吧。

  • 通过method_exchangeImplementations() C函数, 将重写的方法,一一交换成重写的方法。
  • 在这些fd_方法中的实现细节中,需要注意的一点就是,如果对应的fd_indexPathHeightCache设置了automaticallyInvalidateEnabled属性为YES时,对应的方法对高度缓存做相应的处理,重新更新fd_indexPathHeightCache中存储的高度缓存。
  • 当第一次reloadData,或者cell的行数发生变化(增减行,section) ,会先在tableview不处于滚动状态的时候异步计算那些没有被计算过的cell的高度,做预缓存,这个想法非常赞。
  • 使用者需要小心,这些调用是异步的, tableview delegate有可能会在预缓存计算的时候不存在了,导致程序崩溃,所以使用者在tableview需要析构的时候,在对应的tableview controller的dealloc中讲self.tableview.delegate = nil;,确保delegate后续不会是一个野指针。

UITableView+FDTemplateLayoutCell

至此,我们已经分析了几个子类的实现逻辑,唯一剩下一个分类,也是我们使用这个框架的入口 FDTemplateLayoutCell分类。全面了解这个组件近在咫尺。


  • 先来看看我们平时开发中最频繁调用的两个方法
  • (CGFloat)fd_heightForCellWithIdentifier:(NSString )identifier cacheByIndexPath:(NSIndexPath )indexPath configuration:(void (^)(id cell))configuration;
  • (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier cacheByKey:(id)key configuration:(void (^)(id cell))configuration;

  • 这两个方法,分别是对cell通过NSIndexPath 或者 key值 进行高度缓存,读取高度的时候,先从缓存cache中读取,如果缓存中没有,在通过[self fd_heightForCellWithIdentifier:identifier configuration:configuration]方法进行计算高度并加入缓存中。

  • 通过blocks进行配置并计算cell的高度,主要通过[self fd_templateCellForReuseIdentifier:identifier]方法创建一个UITableViewCell的实例templateLayoutCell,最后再把templateLayoutCell放入[self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]中进行计算返回高度。

  • 将所有创建的templateCell放置在一个字典templateCellsByIdentifiers中,并通过runtime将其加入内存中作为属性,实际上,templateCell 也是通过identifier在复用队列中获取复用的。所以,cell在使用前,应先注册为cell的复用对象。
  • 最后调用的[self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]进行高度计算。当然也是最关键的一个操作,既然这是一个高度计算的框架,那么计算的步骤当然是重中之重。

至此,就大致将这个框架分析的差不多了,源码中,对类的实例化均为采用runtime添加AssociatedObject的方式。就不做解释了。


最后

1 2 收藏 评论

相关文章

可能感兴趣的话题



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