tableView的優(yōu)化方案和總結(jié)

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)
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容