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: 

  1. 如果没有打开预缓存开关,则打开该开关,然后异步做一遍预缓存行高计算
  2. 如果缓存了改行的高度则返回行高
  3. 如果么有缓存,则计算行高fd_heightForCellWithIdentifier: configuration,缓存行高并返回

 

fd_heightForCellWithIdentifier: configuration

  1. 首先获取针对这个identifier类型的模板cell,sunny为每一个类型cell都缓存了一个模板cell,这块我还不明白为什么要这么做,为什么不直接用系统dequeue的重用cell来做?
  2. 将需要计算高度的cell数据填充到这个template cell,然后就可以计算cell高度了
  3. 计算tableview.contentView的真实宽度。
    1. 如果有定制accessoryView,则去除这个宽度
    2. 如果有系统accessoryView展示,则去除对应的宽度。
  4. 检查是否是否是自动布局,判断是否自动布局的标准是contentView本身至少存在一个约束。所以我们在CustomCell做约束的时候,需要至少指定contentView的一个约束。
  5. 如果是自动布局,则将contentView的宽度约束添加进去,这样做的目的是让UILabel之类的内容可能多行的控件适应这个宽度折行(当然前提是我们已经设置好了这些控件的布局约束)。然后调用systemLayoutSizeFittingSize来计算高度。最后移除刚才临时添加的contentView宽度约束。
  6. 如果是绝对布局,则custom cell要重写sizeThatFits接口自己提供行高计算。如果没有重写,则会报错。
  7. 如果存在分割线,则添加1px的高度。因为CGSize是基于point,所以高度计算是 1/[UISscreen mainScreen].scale
  8. 返回计算好的高度。

 fd_templateCellForReuseIdentifier

  1. 缓存字典是否存在,没有则创建一个。
  2. 是否缓存了identifier指定类型的cell,如果没有dequeueReusableCellWithIdentifier一个。
  3. 设置celltranslatesAutoresizingMaskIntoConstraints=NO,只支持自动布局和绝对布局两种。
  4. 将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的高度从缓存中删除掉。

posted @ 2015-09-14 11:00  怎么也得过啊  阅读(1392)  评论(0编辑  收藏  举报