UITableView+FDTemplateLayoutCell源码学习笔记
UITableView-FDTemplateLayoutCell是一个自动计算cell高度并缓存从而达到顺滑滚动tableview的效果。github地址戳这里
基本原理是通过缓存每个cell的高度,当tableview回调delegate的heightForRowAtIndexPath的时候,省去了计算cell高度计算,极大的提高了tableview的滑动体验。
因为 heightForRowAtIndexPath这个接口会在reloadData的时候,每个cell都会调用一次,同时滚动tableview的时候,cell从不可见到可见区域也会被调用一次heightForRowAtIndexPath,所以这个API的调用是很频繁的。由于每个cell的高度是动态的,所以每次要做高度计算,都要重新layout一遍,然后得出高度,这块计算量是比较大的。sunny通过将每个cell的计算好的高度缓存起来,下次获取相同位置cell的高度的时候,直接返回缓存的高度。
同时,当第一次reloadData,或者cell的行数发生变化(增减行,section) ,会先在tableview不处于滚动状态的时候异步计算那些没有被计算过的cell的高度,做预缓存,这块非常赞。就是使用者需要小心,由于这块是异步的, tableview delegate有可能会在预缓存计算的时候不存在了,导致程序崩溃,所以使用者在tableview需要析构的时候,在对应的tableview controller的dealloc中讲self.tableview.delegate = nil;,确保delegate后续不会是一个脏对象。
fd_heightForCellWithIdentifier: cacheByIndexPath: configuration:
- 如果没有打开预缓存开关,则打开该开关,然后异步做一遍预缓存行高计算
- 如果缓存了改行的高度则返回行高
- 如果么有缓存,则计算行高fd_heightForCellWithIdentifier: configuration,缓存行高并返回
fd_heightForCellWithIdentifier: configuration
- 首先获取针对这个identifier类型的模板cell,sunny为每一个类型cell都缓存了一个模板cell,这块我还不明白为什么要这么做,为什么不直接用系统dequeue的重用cell来做?
- 将需要计算高度的cell数据填充到这个template cell,然后就可以计算cell高度了
- 计算tableview.contentView的真实宽度。
- 如果有定制accessoryView,则去除这个宽度
- 如果有系统accessoryView展示,则去除对应的宽度。
- 检查是否是否是自动布局,判断是否自动布局的标准是contentView本身至少存在一个约束。所以我们在CustomCell做约束的时候,需要至少指定contentView的一个约束。
- 如果是自动布局,则将contentView的宽度约束添加进去,这样做的目的是让UILabel之类的内容可能多行的控件适应这个宽度折行(当然前提是我们已经设置好了这些控件的布局约束)。然后调用systemLayoutSizeFittingSize来计算高度。最后移除刚才临时添加的contentView宽度约束。
- 如果是绝对布局,则custom cell要重写sizeThatFits接口自己提供行高计算。如果没有重写,则会报错。
- 如果存在分割线,则添加1px的高度。因为CGSize是基于point,所以高度计算是 1/[UISscreen mainScreen].scale
- 返回计算好的高度。
fd_templateCellForReuseIdentifier
- 缓存字典是否存在,没有则创建一个。
- 是否缓存了identifier指定类型的cell,如果没有dequeueReusableCellWithIdentifier一个。
- 设置celltranslatesAutoresizingMaskIntoConstraints=NO,只支持自动布局和绝对布局两种。
- 将cell缓存并返回。
fd_precacheIfNeeded
这个方法会在第一次计算行高,或者tableview的数据发生改变的时候(reloadData,增减行,sections)被异步调用。
这块会对所有需要预先计算的cell都计算一次行高
- 为了避免计算影响到tableview的顺畅滑动,计算只发生在runLoop处于default mode
- 只在ui main thread完全处于空闲状态准备进入waiting状态之前才进行计算。当开始滚动的时候,runloop处于UITrackingRunLoopMode, 这块计算不会被执行。
- 为了让runloop的一次执行时间缩减到最小,每次只向runloop提交一个cell的高度计算任务,执行完才产生下一个。
很喜欢sunny的这块处理,小而精巧。有关RunLoop的深入介绍,参见这篇强文-深入理解RunLoop
FDTemplateLayoutCellAutomaticallyCacheInvalidation
这个category的目的是利用swizzle机制重写了涉及数据变化的delegate方法,例如reloadData, nsertRowsAtIndexPaths等。当回调delegate的这些方法的时候,实际上是回调对应的fd_开头的同名方法,在这些方法里面做对应的缓存高度操作,或者预缓存新产生的cell高度,或者讲删除cell的高度从缓存中删除掉。