tableView 是我們?cè)陂_發(fā)項(xiàng)目中必不可少的控件,相對(duì)于一些列表或者是有規(guī)律的數(shù)據(jù)使用起來(lái)會(huì)實(shí)現(xiàn)數(shù)據(jù)展示快速有方便。那么項(xiàng)目中使用tableVIew我們是否真的做到相關(guān)的性能優(yōu)化呢?本文是自我總結(jié)和學(xué)習(xí)的一個(gè)過程,很多資料都是開發(fā)加上網(wǎng)上借閱的,如果有更好的認(rèn)識(shí)或者問題,各位大神指教。
tableView的優(yōu)化在本人看來(lái)可以從兩個(gè)方面去入手,一個(gè)是從tableView控件本身,二是利用系統(tǒng)運(yùn)行循環(huán)機(jī)制去做相應(yīng)的優(yōu)化
一,從tableView的本身去優(yōu)化,
tableView滑動(dòng)為什么會(huì)卡頓
項(xiàng)目開放中我我們都知道 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
要比- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;先進(jìn)行調(diào)用和計(jì)算,也即是說(shuō)
cell賦值內(nèi)容時(shí),會(huì)根據(jù)內(nèi)容設(shè)置布局,也就可以知道cell的高度,若有1000行,就會(huì)調(diào)用1000次 cellForRow方法,而我們對(duì)cell的處理操作,都是在這個(gè)方法中賦值,布局等等,開銷很大。
UITableViewCell重用機(jī)制
UITableView只會(huì)創(chuàng)建一屏幕(或者一屏幕多一點(diǎn))的cell,其他都是取出來(lái)重用的。每當(dāng)cell滑出屏幕的時(shí)候,就會(huì)放到一個(gè)集合中,當(dāng)要顯示某一位置的cell時(shí),會(huì)先去集合中取,有的話,就直接拿出來(lái)顯示,沒有在創(chuàng)建。
優(yōu)化方法
heightForRow方法處理cellForRow各司其職高。
思路:賦值和計(jì)算布局分離。
cellForRow負(fù)責(zé)賦值,
heightRorRow負(fù)責(zé)計(jì)算高度。
自定義cell繪制
遇到比較復(fù)雜的界面的時(shí)候,如復(fù)雜點(diǎn)的圖文混排,上面的那種優(yōu)化行高的方式可能就不能滿足要求了,當(dāng)然了,由于我的開發(fā)經(jīng)驗(yàn)尚短,說(shuō)實(shí)話,還沒遇到要將自定義的Cell重新繪制。至于這方面,大家可以參考標(biāo)題
按需加載
開發(fā)的過程中,自定義Cell的種類千奇百怪,但Cell本來(lái)就是用來(lái)顯示數(shù)據(jù)的,不說(shuō)100%帶有圖片,也差不多,這個(gè)時(shí)候就要考慮,下滑的過程中可能會(huì)有點(diǎn)卡頓,尤其網(wǎng)絡(luò)不好的時(shí)候,異步加載圖片是個(gè)程序員都會(huì)想到,但是如果給每個(gè)循環(huán)對(duì)象都加上異步加載,開啟的線程太多,一樣會(huì)卡頓,我記得好像線程條數(shù)一般3-5條,最多也就6條吧。這個(gè)時(shí)候利用UIScrollViewDelegate兩個(gè)代理方法就能很好地解決這個(gè)問題。
總結(jié)
1.提前計(jì)算并緩存好高度,因?yàn)閔eightForRow最頻繁的調(diào)用。
2.異步繪制,遇到復(fù)雜界面,性能瓶頸時(shí),可能是突破口
3.滑動(dòng)時(shí)按需加載,這個(gè)在大量圖片展示,網(wǎng)絡(luò)加載時(shí),很管用。(SDWebImage已經(jīng)實(shí)現(xiàn)異步加載)。
4.重用cells。
5.如果cell內(nèi)顯示得內(nèi)容來(lái)自web,使用異步加載,緩存結(jié)果請(qǐng)求。當(dāng)cell中的部分View是非常獨(dú)立的,并且不便于重用的,而且“體積”非常小,在內(nèi)存可控的前提下,我們完全可以將這些view緩存起來(lái)。當(dāng)然也是緩存在模型中。
6.少用或不用透明圖層,使用不透明視圖。對(duì)于不透明的View,設(shè)置opaque為YES,這樣在繪制該View時(shí),就不需要考慮被View覆蓋的其他內(nèi)容(盡量設(shè)置Cell的view為opaque,避免GPU對(duì)Cell下面的內(nèi)容也進(jìn)行繪制)
7.減少subViews。分析Cell結(jié)構(gòu),盡可能的將 相同內(nèi)容的抽取到一種樣式Cell中,前面已經(jīng)提到了Cell的重用機(jī)制,這樣就能保證UITbaleView要顯示多少內(nèi)容,真正創(chuàng)建出的Cell可能只比屏幕顯示的Cell多一點(diǎn)。雖然Cell的’體積’可能會(huì)大點(diǎn),但是因?yàn)镃ell的數(shù)量不會(huì)很多,完全可以接受的
8.少用addView給cell動(dòng)態(tài)添加view,可以初始化的時(shí)候就添加,然后通過hide控制是否顯示。只定義一種Cell,那該如何顯示不同類型的內(nèi)容呢?答案就是,把所有不同類型的view都定義好,放在cell里面,通過hidden顯示、隱藏,來(lái)顯示不同類型的內(nèi)容。畢竟,在用戶快速滑動(dòng)中,只是單純的顯示、隱藏subview比實(shí)時(shí)創(chuàng)建要快得多。
二,利用Runloop優(yōu)化tableView
卡頓原因
tableView加載過多的高清大圖,并且一個(gè)cell加載多張圖片的時(shí)候,拖動(dòng)tableView時(shí),runloop不僅需要處理拖動(dòng)事件,還要處理圖片渲染,從而造成卡頓。
需求:
從網(wǎng)絡(luò)加載高清大圖到UITableViewCell上,而且每個(gè)Cell上面加載多張圖片,當(dāng)cell數(shù)量過多的時(shí)候,我們需要保持流暢度和加載速度。
runloop如何優(yōu)化tableView
把任務(wù)以block塊的方式封裝起來(lái),存放到任務(wù)數(shù)組中,若任務(wù)數(shù)組中的任務(wù)數(shù)超出最大任務(wù)數(shù),則刪除靠前的任務(wù),注冊(cè)runloop的觀察者,在回調(diào)方法里,執(zhí)行任務(wù)數(shù)組中的一個(gè)任務(wù),并刪除執(zhí)行后的任務(wù)。添加timer事件,防止runloop進(jìn)入休眠狀態(tài)
實(shí)現(xiàn)思路
為了解決tableView的卡頓現(xiàn)象,可以runloop一次處理一個(gè)任務(wù),
根據(jù)當(dāng)前的runloop和觀察者的上下文,通過CFRunLoopObserverCreate函數(shù),定義一個(gè)觀察runloop即將進(jìn)入休眠狀態(tài)(BeforeWaiting)時(shí)的觀察者,添加runloop在common模式下的監(jiān)聽,
在觀察者回調(diào)函數(shù)中,根據(jù)觀察者上下文執(zhí)行當(dāng)前對(duì)象任務(wù)數(shù)組中的第一個(gè)任務(wù),任務(wù)執(zhí)行后從數(shù)組中移除,
在繪制cell的方法(即cellForRow)中,調(diào)用添加任務(wù)的方法,在這個(gè)方法的內(nèi)部,將用block代碼塊包裝的任務(wù)添加到數(shù)組中,若超出最大任務(wù)數(shù),則刪除之前的任務(wù),便于觀察者回調(diào)函數(shù)分開執(zhí)行任務(wù),減少對(duì)系統(tǒng)資源的消耗,
最后添加timer或source0 事件,使runloop不進(jìn)入休眠狀態(tài)。
實(shí)現(xiàn)的代碼邏輯
1,定義一個(gè)存放執(zhí)行任務(wù)的block
typedef void(^SaveFuncBlock)(void);//存放定時(shí)任務(wù)
//存放任務(wù)的數(shù)組
@property (nonatomic, strong) NSMutableArray *saveTaskMarr;
//最大任務(wù)數(shù)(超過最大任務(wù)數(shù)的任務(wù)就停止執(zhí)行)
@property (nonatomic, assign) NSInteger maxTasksNumber;
//任務(wù)執(zhí)行的代碼塊
@property (nonatomic, copy) SaveFuncBlock saveFuncBlock;
2,添加runloop
-(void)RunLoopOptimization
{
//1、先獲取當(dāng)前的Runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//定義觀察者
CFRunLoopObserverRef observer;
CFRunLoopObserverContext context = {
0,
(__bridge void *)self,
&CFRetain,
&CFRelease,
NULL
};
//創(chuàng)建觀察者 參數(shù)一:分配空間的方式,參數(shù)二:運(yùn)行循環(huán)狀態(tài),參數(shù)三:是否一直監(jiān)聽,參數(shù)四:優(yōu)先級(jí) 參數(shù)五:回調(diào)函數(shù)的地址,參數(shù)六:上下文
observer = CFRunLoopObserverCreate(kCFAllocatorMalloc, kCFRunLoopBeforeWaiting, YES, 0, &Callback, &context);
//添加觀察者,添加在common模式下面
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
//創(chuàng)建定時(shí)器 (保證runloop回調(diào)函數(shù)一直在執(zhí)行)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(notDoSomething)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
3,通過match_port實(shí)現(xiàn)回調(diào)
//定義一個(gè)回調(diào)函數(shù) 一次RunLoop來(lái)一次
static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
MomentViewController * vcSelf = (__bridge MomentViewController *)(info);
if (vcSelf.saveTaskMarr.count > 0) {
//獲取一次數(shù)組里面的任務(wù)并執(zhí)行
SaveFuncBlock funcBlock = vcSelf.saveTaskMarr.firstObject;
funcBlock();
[vcSelf.saveTaskMarr removeObjectAtIndex:0];
}
}
4,添加任務(wù)
//添加任務(wù)進(jìn)數(shù)組保存
-(void)addTasks:(SaveFuncBlock)taskBlock{
[self.saveTaskMarr addObject:taskBlock];
//超過每次最多執(zhí)行的任務(wù)數(shù)就移出當(dāng)前數(shù)組
if (self.saveTaskMarr.count > self.maxTasksNumber) {
[self.saveTaskMarr removeObjectAtIndex:0];
}
}
- (void)notDoSomething {
// 不做事情,就是為了讓 callBack() 函數(shù)一直相應(yīng)
}