UITableViewCell 高度計(jì)算從混沌初始到天地交泰
本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:
UITableView控件可能是iOS中大家最常用的控件了(滾動(dòng)視圖、cell重用、卡頓優(yōu)化),今天要討論的不是這些高大上的話題,今天的話題只是cell高度的計(jì)算。
* 傳統(tǒng)frame布局下UITableViewCell 高度計(jì)算
* AutoLayout下UITableViewCell高度計(jì)算(iOS6、7)
* UITableViewCell高度計(jì)算之iOS8抽風(fēng)之旅
* UITableViewCell高度計(jì)算之大一統(tǒng)
*第三方庫UITableView-FDTemplateLayoutCell源碼拋析
以下demo都是在cell高度可變的基礎(chǔ)上進(jìn)行的
一、傳統(tǒng)frame布局下UITableViewCell 高度計(jì)算
? ? ? ? 1、史上最傳統(tǒng)的UITableViewCell使用方法(號(hào)稱又笨又老),相信大家都用過這種,純frame布局,cell定制,手動(dòng)傳入數(shù)據(jù)通過手動(dòng)計(jì)算每一行cell的高度,代碼都不好意思上了。
還是上下之前的demo吧!
主要是在UITableViewCell(subCell)中使用一個(gè)靜態(tài)方法傳入數(shù)據(jù)并手動(dòng)計(jì)算內(nèi)容的高度
說到手動(dòng)計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對(duì)應(yīng)的寬高,看下具體的API:
@interface NSString(UIStringDrawing)
// Single line, no wrapping. Truncation based on the NSLineBreakMode.
- (CGSize)sizeWithFont:(UIFont*)fontNS_DEPRECATED_IOS(2_0,7_0,"Use -sizeWithAttributes:");
- (CGSize)sizeWithFont:(UIFont*)font forWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");
// Wrapping to fit horizontal and vertical size. Text will be wrapped and truncated using the NSLineBreakMode. If the height is less than a line of text, it may return
// a vertical size that is bigger than the one passed in.
// If you size your text using the constrainedToSize: methods below, you should draw the text using the drawInRect: methods using the same line break mode for consistency
- (CGSize)sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)sizeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// Uses NSLineBreakModeWordWrap
- (CGSize)sizeWithFont:(UIFont*)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakModeNS_DEPRECATED_IOS(2_0,7_0,"Use -boundingRectWithSize:options:attributes:context:");// NSTextAlignment is not needed to determine size
這個(gè)地方Apple提供了一個(gè)NSString的分類,我們可以通過傳入對(duì)應(yīng)的string 計(jì)算出label的自適應(yīng)寬高,說到底就是使用sizeWithFont:系列重載函數(shù)根據(jù)字符串計(jì)算label的content大小。
代碼中使用:
(NSString一個(gè)傳統(tǒng)的方法sizeWithFont:)來計(jì)算label新的frame,然后更新布局,之后返回一個(gè)預(yù)計(jì)算的高度值
+ (CGFloat)calulateHeightWithtTitle:(NSString*)title
{
CGFloatheight =20;
CGSizelabelSize = [titlesizeWithFont:[UIFont ? systemFontOfSize:17] constrainedToSize:CGSizeMake(300,500)];
height = height + labelSize.height;
returnheight;
}
最終方法的調(diào)用在:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
? ? ? ?return [HomeCell ?calulateHeightWithtTitle:self.dataArray[indexPath.row]];
}
來完成,并且return該float值作為cell的高度。
二、AutoLayout下UITableViewCell高度計(jì)算(iOS6、7)
1、下面介紹第二種方法,使用自動(dòng)布局下的cell高度計(jì)算,總體來講,自動(dòng)布局下 的cell高度計(jì)算歸功于UILabel的布局,自動(dòng)布局下默認(rèn)無需要再指定view的frame,設(shè)置完對(duì)應(yīng)的約束,label會(huì)自動(dòng)根據(jù)內(nèi)容的多少來完成布局。廢話少說先上體驗(yàn)版demo。
上面描述到,傳統(tǒng)frame布局時(shí)間,主要是通過一些列手手動(dòng)計(jì)算cell中l(wèi)abel的寬高,然后在針對(duì)cell中的subView進(jìn)行重新布局,最后得出一個(gè)整體的高度作為cell真實(shí)的高度,那么在自動(dòng)布局中又該如何實(shí)現(xiàn)呢?首先自動(dòng)布局一改了之前frame的概念,自動(dòng)布局中不存在所謂的坐標(biāo) 寬高,只有對(duì)應(yīng)的約束。針對(duì)UILabel來說,自動(dòng)布局下label會(huì)根據(jù)內(nèi)容的多少自適應(yīng)的調(diào)整label的大小,顯示對(duì)應(yīng)的內(nèi)容。這一點(diǎn)先看下UILabel在iOS6以后發(fā)生的變化:
// Support for constraint-based layout (auto layout)
// If nonzero, this is used when determining -intrinsicContentSize for multiline labels
@property(nonatomic)CGFloat ?preferredMaxLayoutWidthNS_AVAILABLE_IOS(6_0);
看到官方的注視,基本也大概有差不多的意思了,這東西實(shí)在autolayout下使用的,大概意思是給多行l(wèi)abel設(shè)置一個(gè)布局時(shí)間優(yōu)先使用的一個(gè)寬度。
在看下UIView的變化
@interfaceUIView (UIConstraintBasedLayoutFittingSize)
/* The size fitting most closely to targetSize in which the receiver's subtree can be laid out while optimally satisfying the constraints. If you want the smallest possible size, pass UILayoutFittingCompressedSize; for the largest possible size, pass UILayoutFittingExpandedSize.
Also see the comment for UILayoutPriorityFittingSizeLevel.
*/
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSizeNS_AVAILABLE_IOS(6_0);
// Equivalent to sending -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: with UILayoutPriorityFittingSizeLevel for both priorities.
意思大概就是說 當(dāng)前的這個(gè)這個(gè)尺寸關(guān)系能夠最佳的適應(yīng)接收器的子樹在滿足自適應(yīng)約束的同時(shí),如果想要一個(gè)最下的尺寸就設(shè)置為:UILayoutFittingCompressedSize;反之設(shè)置:UILayoutFittingExpandedSize。
實(shí)戰(zhàn)應(yīng)用:
自動(dòng)布局下的自適應(yīng)cell高度玩轉(zhuǎn),本教程完全依賴storybord ,依舊在代碼UI領(lǐng)域的媛猿們,需要轉(zhuǎn)變一下思維了。
(1)、創(chuàng)建故事板、初始化好tableview、cell的輸出口等,準(zhǔn)備cell的約束,如圖:
cell上只有一個(gè)label,label的約束如下,大體就是具體上下左右各加上一個(gè)約束,將來在label中放在對(duì)應(yīng)的內(nèi)容文字,自適應(yīng)高度(不要忘了設(shè)置cell的identifier)。
(2)、部分實(shí)現(xiàn)處理代碼
ViewController中部分代理方法
- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
{
return1;
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
? ? ? ?return [self.dataArraycount];
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *cellIdentifier = @"HomeCell";
HomeCell *cell = [tableView ?dequeueReusableCellWithIdentifier:cellIdentifier];
cell.content.text= [self.dataArray ?objectAtIndex:indexPath.row];
CGFloat ?preMaxWaith =[UIScreen ?mainScreen].bounds.size.width-108;
[cell.contentset ?PreferredMaxLayoutWidth:preMaxWaith];
[cell.contentlayout ?IfNeeded];
returncell;
}
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
staticHomeCell*cell =nil;
static dispatch_once_ ?tonceToken;
//只會(huì)走一次
dispatch_once(&onceToken, ^{
cell = (HomeCell*)[tableView ?dequeueReusableCellWithIdentifier:@"HomeCell"];
});
//calculate
CGFloatheight = [cell ?calulateHeightWithtTitle:[self.dataArray objectAtIndex:indexPath.row]desrip:[self.dataArray objectAtIndex:indexPath.row]];
returnheight;
}
HomeCell.m
-(CGFloat)calulateHeightWithtTitle:(NSString*)title desrip:(NSString*)descrip
{
//這里非常重要
CGFloat preMaxWaith =[UIScreen mainScreen].bounds.size.width-108;
[self.contentset PreferredMaxLayoutWidth:preMaxWaith];
//[self.titleLabel setText:title];
//這也很重要
[self.content ?layoutIfNeeded];
[self.content ?setText:descrip];
[self.contentView ?layoutIfNeeded];
CGSizesize = [self.contentView ?systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
//加1是關(guān)鍵
returnsize.height+1.0f;
}
自動(dòng)布局版cell高度計(jì)算OK??!
三、UITableViewCell高度計(jì)算之iOS8抽風(fēng)之旅
1、說到iOS8,在iOS8下如果要計(jì)算cell的高度,代碼越來越少,工作越來越輕松,殊不知表面看起來特別人性的iOS8背地里面也有太多坑的勾當(dāng)(具體原因見后面解釋)。
先上iOS的計(jì)算cell高度的體驗(yàn)demo:
iOS8下計(jì)算cell高度的工作比起之前的版本更加輕松
(1)、故事版拖好對(duì)應(yīng)的VC、cell,接下來上約束,約束如下:
整體來說與2中的約束差不多,分別設(shè)置label距離四周的約束情況。(本篇文章要實(shí)現(xiàn)的本來就是相同的效果,在不同版本下的的實(shí)現(xiàn)方式以及優(yōu)劣的對(duì)比與優(yōu)化。)
設(shè)置好約束后
(2). iOS8的cell高度計(jì)算代碼
設(shè)置tableview的屬性
self.tableView.estimatedRowHeight=44.0;
self.tableView.rowHeight=UITableViewAutomaticDimension;
至此,iOS8cell高度自適應(yīng)計(jì)算OK!! 就是這么簡(jiǎn)單...
四、UITableViewCell高度計(jì)算之大一統(tǒng)
在介紹本欄目之前先上一張表:
heightForRowAtIndexPath:cell高度計(jì)算次數(shù)
由于iOS7之后,tableview 提供了estimatedHeightForRowAtIndexPathCount的API,這就對(duì)cell高度計(jì)算的方法調(diào)用次數(shù)產(chǎn)生了影響。
下面首先說下estimatedHeightForRowAtIndexPathCount :
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView*)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath*)indexPathNS_AVAILABLE_IOS(7_0);
? ? ? ?apple的文檔里面說大概意思是estimatedHeight可以快速的預(yù)估一個(gè)cell的高度值,從而讓table的加載速度更快。整體來說就是tableview在渲染的時(shí)間,他會(huì)首先根據(jù)內(nèi)容計(jì)算每個(gè)cell的高度,從而計(jì)算出tableview的的一個(gè)contentsize(tableview繼承于scrollview),但是如果有一萬行數(shù)據(jù),那么這個(gè)計(jì)算的過程會(huì)非常的卡頓,從而影響table的load的速度,我們可以給cell(除了當(dāng)前需要顯示在屏幕上的cell)設(shè)置一個(gè)預(yù)估的高度值,這樣就大大節(jié)省了計(jì)算高度的損耗與開銷。
有上圖可以看出,iOS7 的tableview對(duì)于cell的高度是有緩存功能的,當(dāng)劃到底部,從底部再往頂部滑動(dòng)時(shí)間,heightForRowAtIndexPath:cell的調(diào)用次數(shù)為0,這說話cell的高度已經(jīng)換存在了內(nèi)存。iOS8、iOS9坑爹的一面在于當(dāng)關(guān)閉高度預(yù)估方法時(shí)間(estimatedHeightForRowAtIndexPathCount),heightForRowAtIndexPath:cell的調(diào)用次數(shù)非常多(我們一般會(huì)在這個(gè)地方計(jì)算根據(jù)內(nèi)容手動(dòng)計(jì)算cell的高度,或者更新cell內(nèi)不各種view的約束),這個(gè)過程如果頻繁的調(diào)用是非常耗損性能的,更悲劇的事造成tableview的卡頓,這個(gè)是最容忍不了的。當(dāng)開啟高度預(yù)估時(shí)間,高度預(yù)估之時(shí)返回了一個(gè)定值,此時(shí)heightForRowAtIndexPath:cell的調(diào)用次數(shù)大大減少,高度計(jì)算的工作也就大大減少,此時(shí)就是我們想要的效果。
此外,這個(gè)地方有一個(gè)可能忽略的問題,當(dāng)我們的工程從原來的iOS7遷移到8再到9的時(shí)間,如果這個(gè)地方不做進(jìn)一步的優(yōu)化,之前的代碼在新的系統(tǒng)下跑起來結(jié)果就不想而之了,為了能夠兼容到所有系統(tǒng)下的cell高度計(jì)算,推薦一個(gè)新的工具
UITableView-FDTemplateLayoutCell
參考博客:具體教程如
(1)、FDTemplateLayoutCell 使用教程
1. 下載FDTemplateLayoutCell第三方庫,導(dǎo)入工程
2. 導(dǎo)入頭文件?
3. 使用FD實(shí)現(xiàn)heightForRowAtIndexPath方法,如下:
4、大功告成,使用fd一次性解決的iOS 6、7、8、9中的cell高度計(jì)算問題,F(xiàn)D采用自帶的緩存的機(jī)制,無需多次調(diào)用heightForRowAtIndexPath時(shí)間的cell高度計(jì)算開銷
1、FDTemplateLayoutCell之所以能夠做到兼容到所有的系統(tǒng)版本下的tableview,主要在于它維護(hù)了一套自己的cell高度緩存,同時(shí)有效的利用了tableview的高度預(yù)估的功能。從新定義新的cell高度緩存策略,這一點(diǎn)解決了只有iOS7下系統(tǒng)才會(huì)主動(dòng)緩存cell高度的這一難點(diǎn),有了FDiOS8、9下也能使用緩存高度
2、開啟UITableView高度預(yù)估功能,優(yōu)化heightForRowAtIndexPath的調(diào)用累計(jì)次數(shù)
(tableView:estimatedHeightForRowAtIndexPath: NS_AVAILABLE_IOS(7_0);)
由上可以看出estimatedHeightForRowAtIndexPath是iOS7才有的,iOS6是沒有這個(gè)代理的,這個(gè)時(shí)間不僅要問,難道要iOS必須支持iOS7+以上才能使用,答案當(dāng)然不是,系統(tǒng)的API早已做了優(yōu)化,estimatedHeightForRowAtIndexPath在iOS6下面默認(rèn)是可以被忽略的,不會(huì)因?yàn)榘姹镜膯栴}引起異常。在iOS6下高度計(jì)算的策略會(huì)跟iOS8、9下有點(diǎn)類似,使用FD自己提供的緩存,也能達(dá)到有效的減少計(jì)算cell高度帶來的開銷。
五、FDTemplateLayoutCell源碼拋析
談到FD,首先熟悉下之前的一個(gè)知識(shí)點(diǎn),?iOS知識(shí)點(diǎn)整理-RunLoop??赡苡行├仙U劻?,也有可能部分童鞋看到直接暈掉了,其實(shí)大多iOS里面大多第三方庫的手段無外乎就是runtime(這個(gè)東西在java中叫reflact,java里面有AOP , iOS 其實(shí)跟這個(gè)差不多)、CF這些黑魔法之類的東西來進(jìn)行偷天換日、移花接木。
小結(jié):iOS 中的runloop
? ? ? ? 1、NSRunLoop提供了面向?qū)ο蟮腁PI,但這些API不是線程安全的
? ? ? ? 2、CFRunLoopRef提供了純C函數(shù)的API,所有這些API都是線程安全的
NSRunLoop是cocoa提供的,這個(gè)東西可能大多人還是經(jīng)常使用的,cell里面更新異步下載成功的圖片、啟用一個(gè)timer追加到當(dāng)前的應(yīng)用循環(huán)中、啟用一個(gè)常駐線程等;
CFRunLoopRef可能就相對(duì)陌生些,CF開頭跟定就是CoreFoundation中定義的,可以暫時(shí)理解為每個(gè)線程都有一個(gè)對(duì)應(yīng)的runloop, 在一個(gè)runloop中可以有多種Model(模式),每個(gè)Mode又包含若干個(gè)source/Timer/Observe .
程序執(zhí)行的時(shí)間當(dāng)前runloop 只能存在一種Model,如果發(fā)生場(chǎng)景切換需要退出當(dāng)前Model,進(jìn)入下一個(gè)Model
系統(tǒng)一共提供了五種model:
? ? ? ? ?NSDefaultRunLoopMode: ? ?App默認(rèn)Mode,當(dāng)沒有接收到ScrollView滾動(dòng)是,主線程通常使用這個(gè)Mode
? ? ? ? ?NSTrackingRunLoopMode: ?到接收到ScroolView或其子類的時(shí)候,主線程就會(huì)切換到這個(gè)模式下運(yùn)行。
? ? ? ? ?UIInitializationRunLoopMode:當(dāng)App啟動(dòng)時(shí)使用的第一個(gè)Mode,當(dāng)啟動(dòng)完成后不再使用。
? ? ? ? ? NSRunLoopCommonModes,是一個(gè)tag,本質(zhì)上不是一個(gè)Mode,默認(rèn) ? ? ? ? ? ? ? ? ? ?NSDefaultRunLoopMode和NSTrackingRunLoopMode都綁定這個(gè)tag。(應(yīng)用場(chǎng)景:有時(shí)候我們需要添加一個(gè)NSTimer在RunLoop,在這時(shí)需要制定一個(gè)Modes,現(xiàn)在的需求是:我們既要在默認(rèn)模式下要監(jiān)聽,在滾動(dòng)模式下也要監(jiān)聽,但只能制定一個(gè)模式,這是可以制定這個(gè)CommonMode)
? ? ? ? ?GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部的Mode,通常做不到。
處理不同事件使用不同的Mode,可以最大限度的把性能的最大化處理不同分類的事件,提高性能。
知道了這些,我們可以在此處做文章,我們發(fā)現(xiàn)UITableView(繼承UIScrollView)不滾動(dòng)時(shí)間是NSDefaultRunLoopMode 模式,滾動(dòng)時(shí)間是NSTrackingRunLoopMode模式,我們可以 通過注冊(cè)觀察者來實(shí)現(xiàn)讓tableview不滾動(dòng)的時(shí)間再去計(jì)算所有的cell的高度,一旦當(dāng)tableview開始滾動(dòng)我們?cè)偃ト〉脮r(shí)間著時(shí)間緩存池里面已經(jīng)計(jì)算 的差不多了,也就是說盡最大可能讓tableview不滾動(dòng)的時(shí)間處理好所有的cell高度,緩存下來,等到滑動(dòng)tableview的時(shí)間優(yōu)先從緩存取,這個(gè)地方盡最大避免了邊滑動(dòng)邊計(jì)算cell高度卡頓問題。
完成了這個(gè)知識(shí)點(diǎn),接下來就是處理好緩存邏輯的事情了。
1、首先對(duì)于FD來說,維護(hù)cell的高度需要將計(jì)算過的cell的高度放進(jìn)一個(gè)二維數(shù)組里面(section row)
? ? FD中存在一個(gè)可維護(hù)的NSMutableArray sections; 可以先理解為一個(gè)嵌套起來的數(shù)組是一個(gè)二位的數(shù)組,接下來的時(shí)間會(huì)把tableview 某個(gè)section下的row對(duì)應(yīng)的行對(duì)應(yīng)的高度存在這個(gè)位置,
2、tableView渲染的時(shí)間,統(tǒng)一還是會(huì)走 heightForRowAtIndexPath方法的,我們只需要在此處直接獲取到cache里面的已經(jīng)存儲(chǔ)的高度就行了,在此處避開cell的高度邏輯計(jì)算過程就到達(dá)了我們的目的。
FD組件已經(jīng)作了很好的封裝,在heightForRowAtIndexPath中調(diào)用fd計(jì)算高度的方法,
- (CGFloat)fd_heightForCellWithIdentifier:(NSString*)identifier cacheByIndexPath:(NSIndexPath*)indexPath configuration:(void(^)(id))configuration
{
if(!identifier || !indexPath) {
return0;
}
// Enable auto cache invalidation if you use this "cacheByIndexPath" API.
if(!self.fd_autoCacheInvalidationEnabled) {
self.fd_autoCacheInvalidationEnabled=YES;
}
// Enable precache if you use this "cacheByIndexPath" API.
if(!self.fd_precacheEnabled) {
self.fd_precacheEnabled=YES;
// Manually trigger precache only for the first time.
[selffd_precacheIfNeeded];
}
// Hit the cache
if([self.fd_cellHeightCachehasCachedHeightAtIndexPath:indexPath]) {
[selffd_debugLog:[NSStringstringWithFormat:
@"hit cache - [%@:%@] %@",
@(indexPath.section),
@(indexPath.row),
@([self.fd_cellHeightCachecachedHeightAtIndexPath:indexPath])]];
return[self.fd_cellHeightCachecachedHeightAtIndexPath:indexPath];
}
// Call basic height calculation method.
CGFloatheight = [selffd_heightForCellWithIdentifier:identifierconfiguration:configuration];
[selffd_debugLog:[NSStringstringWithFormat:
@"cached - [%@:%@] %@",
@(indexPath.section),
@(indexPath.row),
@(height)]];
// Cache it
[self.fd_cellHeightCachecacheHeight:heightbyIndexPath:indexPath];
returnheight;
}
這個(gè)步驟中,基本可以看出FD的使用策略,首先開啟一個(gè)[selffd_precacheIfNeeded]的操作(這個(gè)過程是做了一個(gè)預(yù)計(jì)算cell高度的操作,稍后詳解),接下來的過程就是從緩存池中根據(jù)IndexPath(cell高度預(yù)存儲(chǔ)在一個(gè)模擬的二維數(shù)組中)去讀取cell的高度,如果cache命中就直接返回cell高度,否則執(zhí)行:
// Call basic height calculation method.
CGFloatheight = [selffd_heightForCellWithIdentifier:identifierconfiguration:configuration];
去手動(dòng)計(jì)算一次cell的高度,計(jì)算獲得后存入緩存池
// Cache it
[self.fd_cellHeightCachecacheHeight:heightbyIndexPath:indexPath];
最后返回高度。
3、介紹FD的緩存池
FD在這個(gè)地方利用了runloop的黑魔法,通過注冊(cè)一個(gè)觀察者,當(dāng)tableview停止滑動(dòng)的他會(huì)主動(dòng)去計(jì)算當(dāng)前數(shù)據(jù)源中的剩余的cell的高度,計(jì)算完以后存儲(chǔ)在緩存池中,這個(gè)調(diào)用就是(2)中的
// Enable precache if you use this "cacheByIndexPath" API.
if(!self.fd_precacheEnabled) {
self.fd_precacheEnabled=YES;
// Manually trigger precache only for the first time.
[selffd_precacheIfNeeded];
}
在這個(gè)開啟調(diào)用中,通過一些列手段將tableview不滾動(dòng)時(shí)間去計(jì)算cell的高度(具體原理此處省略),計(jì)算后存入緩存池sections,sections是一個(gè)可變數(shù)組,筆者顯示把這個(gè)理解成一個(gè)內(nèi)存存儲(chǔ)元素是可變數(shù)組的數(shù)組(模擬一個(gè)二維數(shù)組),F(xiàn)D先是給自己增加了一個(gè)屬性sections作為緩存池,通過objc_setAssociatedObject給分類增加屬性的此處就不介紹了,
[selfbuildHeightCachesAtIndexPathsIfNeeded:@[indexPath]];
self.sections[indexPath.section][indexPath.row] =@(height);
// Build every section array or row array which is smaller than given index path.
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath*indexPath,NSUIntegeridx,BOOL*stop) {
NSAssert(indexPath.section>=0,@"Expect a positive section rather than '%@'.",@(indexPath.section));
for(NSIntegersection =0; section <= indexPath.section; ++section) {
if(section >=self.sections.count) {
self.sections[section] =@[].mutableCopy;
}
}
NSMutableArray*rows =self.sections[indexPath.section];
for(NSIntegerrow =0; row <= indexPath.row; ++row) {
if(row >= rows.count) {
rows[row] =@(_FDTemplateLayoutCellHeightCacheAbsentValue);
}
}
}];
此處主要是構(gòu)造一個(gè)緩存池,通過在sections中存儲(chǔ)一個(gè)NSMutableArray,模擬一個(gè)二維的數(shù)組
通過indexPath的section 和 row作為下標(biāo),構(gòu)造完成直接將高度存進(jìn)去:
self.sections[indexPath.section][indexPath.row] =@(height);
至此緩存池結(jié)束
4、至此FD的核心手段大題已經(jīng)講完,接下來就是考慮到tableview的增刪改插的時(shí)間的處理問題,這一系列的動(dòng)作都會(huì)對(duì)緩存池的更新有一定的影響,F(xiàn)D已經(jīng)做了最大的限度的優(yōu)化,依舊runtime, swizzling的魔法就不多解釋了。
dispatch_once(&onceToken, ^{
SELselectors[] = {
@selector(reloadData),
@selector(insertSections:withRowAnimation:),
@selector(deleteSections:withRowAnimation:),
@selector(reloadSections:withRowAnimation:),
@selector(moveSection:toSection:),
@selector(insertRowsAtIndexPaths:withRowAnimation:),
@selector(deleteRowsAtIndexPaths:withRowAnimation:),
@selector(reloadRowsAtIndexPaths:withRowAnimation:),
@selector(moveRowAtIndexPath:toIndexPath:)
};
for(NSUIntegerindex =0; index
SELoriginalSelector = selectors[index];
SELswizzledSelector =NSSelectorFromString([@"fd_"stringByAppendingString:NSStringFromSelector(originalSelector)]);
MethodoriginalMethod =class_getInstanceMethod(self, originalSelector);
MethodswizzledMethod =class_getInstanceMethod(self, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
小結(jié):
FD是一個(gè)封裝的很完美的庫,其實(shí)從一開始使用這個(gè)庫就喜歡上了,作者是百度的sunnyxy,另一方面iOS中runtime仍舊是一個(gè)很強(qiáng)大的東西,大多的第三方庫無非都是基本objc runtime做的一些便捷優(yōu)化,但是一個(gè)優(yōu)秀的第三方庫需要作者不斷的完善和大家的共同努力。