UITableView-FDTemplateLayoutCell 源碼分析
以下簡稱 UITableView-FDTemplateLayoutCell
為 FDT
计济。
FDT
FDT 的作用在我看來就是可以緩存 Cell 高度。如果你還不知道為什么需要緩存 Cell 高度的話胁住,可以先看下這篇文章 UITableViewCell 自動高度五芝,了解下 iOS8 開始如何實現(xiàn) Cell 的自動算高痘儡,并且為什么需要緩存已經計算過的 Cell 的高度。
那么只是實現(xiàn)緩存高度的話枢步,為什么還需要分析 FDT 的源碼呢沉删?因為 FDT 源碼寫得很好,要不那么多 star 呢醉途、群眾的眼睛是雪亮的矾瑰。就這一點就足以讓我們分析下 FDT 是如何實現(xiàn)代碼的。
明確目標
通過 UITableViewCell 自動高度隘擎,我們知道了要實現(xiàn)高度緩存我們需要做的工作如下:
- 決定合適的 Cache Key
- 選取合適的 Cache Storage
- 在 Delete/Insert 發(fā)生時調整緩存數據
于是我們就可以以這幾個為目標分析下它們在 FDT 中的實現(xiàn)殴穴。
Cache Key
在 UITableViewCell 自動高度 中我們已經知道了,indexPath
是可以作為 Cache Key 的货葬,那是我們但從 Cell 角度考慮后的結論采幌。但是 FDT 是很用心的,它除了提供了 indexPath
作為 Cache Key 的方式震桶,還提供了另外一個 API:
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier
cacheByKey:(id<NSCopying>)key
configuration:(void (^)(id cell))configuration;
這個 API 可以讓我自由的決定 Cache Key 的來源休傍,并且 FDT 還很貼心的告訴我們,可以使用 unique entity identifier
蹲姐。
我們知道數據庫中的內容可以通過 ORM 變成 relational entities磨取,比如你有一個 Student 表,里面每一行都是一個學生的信息柴墩,于是 ORM 之后每行就是一個 entity忙厌,而 primary key(通常就是 autoincrement-id) 就可以作為 Cache Key。
這是 FDT 的相關例子:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Entity *entity = self.entities[indexPath.row];
return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByKey:entity.uid configuration:^(id cell) {
// configurations
}];
}
Cache Storage
在 UITableViewCell 自動高度 中我們已經分析了一個可以作為 Storage 的候選江咳,最后得出 objc_setAssociatedObject
和 NSMutableArray
性能要大幅度優(yōu)于 NSCache
慰毅。那么我們可以看看 FDT 的實現(xiàn)是不是符合我們的期望。
通過閱讀源碼扎阶,我們會發(fā)現(xiàn)在 FDT 中同時使用了 objc_setAssociatedObject
和 NSMutableArray
汹胃。
objc_setAssociatedObject
先說說使用 objc_setAssociatedObject
的地方,分別是:
我們就看兩個典型的东臀,它們有下面的特性:
- lazy initialized
-
OBJC_ASSOCIATION_RETAIN_NONATOMIC
到了 UITableView 實例上
這么做的好處呢着饥?很明顯啊:
- lazy initialized惰赋,就是在用到相關的緩存策略時才會初始化其 Cache Storage
- 將 Cache 的釋放托管給了 UITableView 實例的生命周期宰掉,不用管釋放內存的事情了
也是由于第 2 點優(yōu)勢(或者說需求)吧呵哨,那些地方才用了 objc_setAssociatedObject
。所以說具體用什么是酌情而定的轨奄,沒有銀彈孟害。
NSMutableArray
那么哪里用了 NSMutableArray
來作為 Cache Storage 呢?分別是:
還是就看兩個典型挪拟。我們注意到挨务,不論是 KeyedHeightCache
和 IndexPathHeightCache
都將緩存數據放到了 NSMutableArray
。為什么這里就不用 objc_setAssociatedObject
了呢玉组?
回憶我們在 UITableViewCell 自動高度 中分析的谎柄,如果使用了 indexPath
作為 Cache Key,那么在發(fā)生了 Delete/Inert
之后惯雳,緩存中的 Cache Key
就『不準』了朝巫,需要進行相應的變動,那么采用了 NSMutableArray
它能為我們自動的變動石景。
另外在采用 IndexPathHeightCache
時劈猿,F(xiàn)DT 的緩存是一個二維的數組,頭一維定位到 Section潮孽,后一維定位到 Row糙臼,這樣就可以同時管到 Sections
和 Rows
的數據變動。為什么這里要提一下呢恩商,因為一般說到使用 indexPath
做 Cache Key,那么首先想到的就是:
cacheKey = fmt("%d-%d", indexPath.section, indexPath.row)
但是這樣的 Key 生成方式不能適應我們這里需要同時靈活處理 Sections
和 Rows
變動的情況必逆。
在 Delete/Insert 發(fā)生時調整緩存數據
這一步其實是 FDT 為我們做得最重要的一步怠堪,因為自己實現(xiàn)緩存存儲還是相對簡單的,但是在一有 Section/Rows 發(fā)生 Delete/Insert
就及時更改緩存數據是有點麻煩的名眉。
如果讓我去實現(xiàn)在 Section/Row 發(fā)生 Delete/Insert
就及時更改緩存數據粟矿,我會這么做(請叫我反面教材??):
- 繼承
UITableView
產生MCUITableView
- 在
MCUITableView
中重寫涉及 Section/Row 的Delete/Insert
的方法以調整緩存數據 - 同時
MCUITableView
會向外暴露一個屬性mcDelegate
,這個屬性是UITableViewDelegate
類型损拢,使用者需要設置這個屬性陌粹。在我的重寫方法中發(fā)現(xiàn)使用者實現(xiàn)了UITableViewDelegate
中的同名方法時會調用它們,這樣使得使用起來和UITableView
一樣
既然是反面教材福压,那么這個方案的缺點是:
- 實現(xiàn)起來不靈活掏秩,多處重寫
- 使用起來不靈活,需要更改很多已有的代碼荆姆,原來繼承 UITableView 的現(xiàn)在需要繼承
MCUITableView
FDT 使用 Category 的方式提供了便捷的 API蒙幻,肯定不能在這里掉鏈子。重寫肯定是繞不開的胆筒,F(xiàn)DT 采用了更加靈活的方式來完成重寫 method_exchangeImplementations
邮破,代碼在 L156,通過將 UITableView 的相關方法使用 method_exchangeImplementations
和 FDT 自己的方法調換一下,真是很巧妙的抒和。
IndexPathHeightCache or KeyedHeightCache
IndexPathHeightCache
和 KeyedHeightCache
在實現(xiàn)和使用上還有些區(qū)別:
-
IndexPathHeightCache
在實現(xiàn)上需要在 Section/Row 發(fā)送變動時調整緩存矫渔,而KeyedHeightCache
不需要 -
IndexPathHeightCache
使用了indexPath
作為 Key,而KeyedHeightCache
需要你自己確保 Key 是唯一的 - 如果你自己可以確保 Key 是唯一的摧莽,那么
KeyedHeightCache
肯定是會稍微快點的(因為不需要調整緩存)
estimatedRowHeight
estimatedRowHeight 的好處我們在 UITableViewCell 自動高度 中已經說過了庙洼。
但是,你用了 FDT 之后范嘱,就不要設置這個值了送膳,F(xiàn)DT 提倡的就是讓 UITableView 一次計算所有的 Cell's height,原文見 cell-height-calculation
估算高度設計初衷是好的丑蛤,讓加載速度更快叠聋,那憑啥要去侵害滑動的流暢性呢,用戶可能對進入頁面時多零點幾秒加載時間感覺不大受裹,但是滑動時實時計算高度帶來的卡頓是明顯能體驗到的碌补,個人覺得還不如一開始都算好了呢(iOS8更過分,即使都算好了也會邊劃邊計算)
如果你非要用的話棉饶,反正我在 FDT 提供了 Demo 中設置了結果是這樣:
如果非要用的話三思吧??
疑惑
目前發(fā)現(xiàn)的 FDT 源碼中這一處關于 methodSignatureForSelector
讓我很困惑調用的作用是什么厦章,見 L122,我注釋掉也沒發(fā)現(xiàn)什么問題 ??照藻,一定是我打開的方式不對袜啃,總之希望有明白可以告訴我吧,不甚感激??幸缕。
最后群发,happy coding!