UITableView+FDTemplateLayoutCell 原始碼探究

發表於2016-12-17
  • 在我們日常的業務中,常常伴隨大量的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的方式。就不做解釋了。


最後

相關文章