iOS學(xué)習(xí)筆記-TableView性能優(yōu)化篇1

TableView相信只要是做iOS開發(fā)的就不會(huì)陌生,目前大多數(shù)iOS的app都是采用TabBar+NavigationBar+TableViewController這一主流框架,既然用的這么頻繁,肯定就會(huì)在開發(fā)過程中碰到一些問題--比如屏幕掉幀、卡頓等現(xiàn)象。這些現(xiàn)象大幅度的降低了用戶的性能體驗(yàn),并提高了crash的頻率。因此如何能優(yōu)化好tableView就非??简?yàn)程序猿們的功底了。

本猿~啊呸,本人就在開發(fā)公司項(xiàng)目的時(shí)候遇到這類問題,當(dāng)快速滑動(dòng)tableView并且cell中有大量圖片和其他控件需要加載時(shí),就會(huì)出現(xiàn)嚴(yán)重掉幀(我們公司的項(xiàng)目當(dāng)時(shí)大量采用xib現(xiàn)在逐漸用手寫代碼代替),有時(shí)還會(huì)crash。由于當(dāng)時(shí)項(xiàng)目比較趕進(jìn)度,所以沒有時(shí)間去優(yōu)化性能,這種情況直到功能基本完善為止,花了大量功夫進(jìn)行性能優(yōu)化。

接下來我會(huì)根據(jù)tableView的delegate以及dataSource方法的執(zhí)行順序進(jìn)行一步一步的講解。

首先當(dāng)一個(gè)tableView需要顯示內(nèi)容的時(shí)候,首先會(huì)發(fā)送網(wǎng)絡(luò)請(qǐng)求,向服務(wù)器請(qǐng)求數(shù)據(jù),然后將數(shù)據(jù)轉(zhuǎn)為我們可以使用的model后進(jìn)行reload操作,接下來會(huì)向delegate和dataSource請(qǐng)求數(shù)據(jù)。這時(shí)候會(huì)先調(diào)用- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section這個(gè)方法(假設(shè)section為1)。根據(jù)model獲取cell的行數(shù)然后調(diào)用-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath,根據(jù)model計(jì)算出cell的高度。由于tableView是繼承自scrollView,所以tableView也會(huì)有contentSize屬性。它的contentSize取決于所有cell的高度和。和scrollView有一點(diǎn)不同,tableView只會(huì)管理可視的cell高度,這樣做的目的是避免不必要的性能開銷。

大多數(shù)情況下我們是將model直接傳給cell然后在cell里進(jìn)行計(jì)算各控件的相對(duì)位置(利用aotulayout和xib)比如:

-(void)setModel:(ImageCellModel *)model{? ? _model = model;self.detailsLabel.text= model.news;self.priceLabel.text= [NSStringstringWithFormat:@"¥%@",model.money];NSString*iconStr = model.User.headIcon;// self.portraitImgView.layer.cornerRadius = 15;//? self.portraitImgView.layer.masksToBounds = YES;if(![iconStr isEqualToString:@""]) {? ? ? ? [self.portraitImgViewsd_setImageWithURL:[NSURLURLWithString:iconStr]];? ? }else{self.portraitImgView.image= [UIImageimageNamed:@"defaultPortrait@2x.png"];? ? }self.nicknameLabel.text= model.User.nickName;if([model.User.rankisEqualToString:@"0"]) {self.rankImgView.image= [UIImageimageNamed:@""];? ? }}

但是這樣做假如滑動(dòng)比較快,且內(nèi)部控件比較復(fù)雜會(huì)導(dǎo)致CPU的計(jì)算量過大,從而導(dǎo)致掉幀??隙〞?huì)有人疑惑為什么會(huì)掉幀,這是因?yàn)镚PU 一個(gè)機(jī)制叫做垂直同步(簡(jiǎn)寫也是 V-Sync),當(dāng)開啟垂直同步后,GPU 會(huì)等待顯示器的 VSync 信號(hào)發(fā)出后,才進(jìn)行新的一幀渲染和緩沖區(qū)更新。這樣能解決畫面撕裂現(xiàn)象,也增加了畫面流暢度,但需要消費(fèi)更多的計(jì)算資源,也會(huì)帶來部分延遲。當(dāng)GPU發(fā)出垂直同步即VSync信號(hào)后,CPU開始進(jìn)行內(nèi)部控件的創(chuàng)建、布局、解碼和控件的相對(duì)位置計(jì)算。然后將計(jì)算好的內(nèi)容交給GPU進(jìn)行變換、合成、渲染。然后等待下一個(gè)VSync信號(hào)。(這段理論部分來自于YY大神)假如在VSync信號(hào)發(fā)出后,CPU進(jìn)行計(jì)算的時(shí)間過長(zhǎng),或者GPU進(jìn)行渲染的時(shí)間過長(zhǎng)導(dǎo)致兩段時(shí)間加起來超過了1個(gè)VSync周期,就會(huì)將這一幀動(dòng)畫丟棄,并維持上一幀的畫面從而導(dǎo)致掉幀。

那么我們?nèi)绾芜M(jìn)行優(yōu)化呢?

最終目的:平衡CPU和GPU的壓力。正確地利用了CPU和GPU資源,使它們均勻地負(fù)載,這樣子做FPS會(huì)保持在60幀。避免出現(xiàn)CPU滿載GPU低負(fù)載或者GPU滿載CPU低負(fù)載的情況。

如何避免出現(xiàn)CPU滿載GPU低負(fù)載呢?

1.不要用AutoLayout,不要用AutoLayout,不要用AutoLayout(這里的情景是子視圖較多的情況下),重要的事情說3遍。我們進(jìn)行手動(dòng)布局可能會(huì)沒那么方便,但是通過簡(jiǎn)單的加減乘除就可以獲取控件相對(duì)位置和cell的高度。盡管蘋果推薦使用AutoLayout。但是對(duì)于那些比較古老的設(shè)備比如我的5S,CPU通過AutoLayout計(jì)算布局會(huì)比較吃力,尤其是cell內(nèi)部的控件數(shù)量較多的時(shí)候。使用的子視圖越多,AutoLayout的效率越低,這是事實(shí)。那么為什么AutoLayout相對(duì)低效呢。是因?yàn)樗鶕?jù)底層“Cassowary”的約束求解系統(tǒng)進(jìn)行約束計(jì)算,從而得到一個(gè)唯一解,這時(shí)AutoLayout才不會(huì)報(bào)警告或錯(cuò)誤(相信拖控件的同學(xué)肯定遇到過各種黃色警告和紅包約束沖突吧)。假如內(nèi)部的子控件越多,它需要進(jìn)行的線性或非線性計(jì)算量越大,需要求解的約束越多,CPU計(jì)算耗費(fèi)大量時(shí)間從而導(dǎo)致超過了一個(gè)VSync周期。相反的,假如我們進(jìn)行手動(dòng)布局,都是非常簡(jiǎn)單的線性計(jì)算,CPU就不用浪費(fèi)那么時(shí)間,CPU的壓力不會(huì)很大,從而平衡了CPU的負(fù)載。

tips :我們可以在tableView進(jìn)行網(wǎng)絡(luò)請(qǐng)求成功后立刻進(jìn)行后臺(tái)的布局計(jì)算。比如是我,利用AFN請(qǐng)求數(shù)據(jù)成功后會(huì)在success 的block里面進(jìn)行后臺(tái)的計(jì)算:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{NSMutableArray*modelAry = [ImageCellModel mj_objectArrayWithKeyValuesArray:responseObject[@"data"]];NSMutableArray*modelFrameAry = [selfmodelFramesWithModelAry:modelAry];dispatch_async(dispatch_get_main_queue(), ^{//這里面把計(jì)算好的frameModel返回給主線程并執(zhí)行正常的操作});- (NSMutableArray*)modelFramesWithModelAry:(NSArray*)modelArray{NSMutableArray*frameModels = [NSMutableArrayarray];for(ImageCellModel *modelinmodelArray) {? ? ? ? HomeModelFrame *modelFrame = [[HomeModelFrame alloc] init];? ? ? ? modelFrame.model= model;? ? ? ? [frameModels addObject:modelFrame];? ? }returnframeModels;}

上面代碼會(huì)將請(qǐng)求到的JSON數(shù)組轉(zhuǎn)換為model數(shù)組,然后將model數(shù)組里的model轉(zhuǎn)換為modelFrame(就是根據(jù)model的各個(gè)屬性計(jì)算出frame):

#import@classImageCellModel;@interfaceHomeModelFrame:NSObject@property(nonatomic,strong)ImageCellModel *model;@property(nonatomic,assign)CGRectavatarFrame;@property(nonatomic,assign)CGRectnameFrame;@property(nonatomic,assign)CGRectpriceFrame;@property(nonatomic,assign)CGRectphotosFrame;@property(nonatomic,assign)CGRectlabelFrame;@property(nonatomic,assign)CGRectdescriptionFrame;@property(nonatomic,assign)CGRecttimeAndDisFrame;@property(nonatomic,assign)CGFloatcellHeight;@property(nonatomic,assign)CGRectdateAndOurLabelFrame;@end//在.m中-(void)setModel:(ImageCellModel *)model{? ? _model = model;CGFloatcellW = [UIScreenmainScreen].bounds.size.width;if(model.PicList.count) {? ? ? ? _photosFrame =CGRectMake(0,0, cellW , (cellW ) /2);? ? ? ? }else{? ? ? ? _photosFrame =CGRectMake(0,0, cellW,50);? ? }? ? ? ? _priceFrame =CGRectMake(cellW -60,CGRectGetMaxY(_photosFrame) -28,60,20);? ? ? ? _avatarFrame =CGRectMake(15,CGRectGetMaxY(_photosFrame) -19,38,38);? ? ? ? _labelFrame =CGRectMake(CGRectGetMaxX(_avatarFrame) +10,CGRectGetMaxY(_photosFrame) -9,18,18);? ? ? ? _descriptionFrame =CGRectMake(CGRectGetMaxX(_avatarFrame),CGRectGetMaxY(_labelFrame) +11, cellW -CGRectGetMaxX(_avatarFrame) -17,13);? ? ? ? _dateAndOurLabelFrame =CGRectMake(CGRectGetMaxX(_avatarFrame),CGRectGetMaxY(_descriptionFrame) +14,180,11);? ? ? ? _timeAndDisFrame =CGRectMake(cellW -150,CGRectGetMaxY(_descriptionFrame) +14,150,11);? ? ? ? ? ? _cellHeight =CGRectGetMaxY(_timeAndDisFrame) +9;}

提前計(jì)算后各個(gè)控件的frame并把cell的高度提前緩存起來等到調(diào)用-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath時(shí)直接return _cellHeight,沒有任何計(jì)算量,從而減輕CPU的負(fù)載。

假如想進(jìn)一步優(yōu)化,可以嘗試調(diào)用控件的view.layer.displaysAsynchronously屬性為YES。

之前我們調(diào)用了dataSource和delegate的兩個(gè)關(guān)于row和height的方法,接下來tableView會(huì)調(diào)用- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath這個(gè)返回創(chuàng)建并返回特定row的cell。在這個(gè)方法內(nèi)部,我們對(duì)cell里的每個(gè)控件進(jìn)行賦值并計(jì)算各個(gè)控件的布局。只需要把各個(gè)控件的frame指向之前已經(jīng)計(jì)算好的各個(gè)modelFrame就可以了,不需要進(jìn)行多余的布局計(jì)算,然后對(duì)每個(gè)控件的內(nèi)容進(jìn)行一一賦值,這樣子就能在調(diào)用cellForRowAtIndexPath這個(gè)方法的時(shí)候迅速的返回一個(gè)cell。

如何避免出現(xiàn)GPU滿載CPU低負(fù)載呢?

1.當(dāng)多個(gè)視圖重疊時(shí),GPU會(huì)對(duì)其進(jìn)行合成渲染,而渲染最慢的操作之一是混合,因此當(dāng)視圖結(jié)構(gòu)太復(fù)雜就會(huì)消耗大量GPU資源,所以當(dāng)一個(gè)控件本身是不透明的,注意設(shè)定opaque = YES,這樣子可以避免無用的alpha通道合成,降低GPU負(fù)載。

2.對(duì)控件設(shè)置cornerRadius后對(duì)其進(jìn)行clip或mask操作時(shí),會(huì)導(dǎo)致offscreen rendering,而這個(gè)是在GPU中進(jìn)行的,所以快速滑動(dòng)tableView時(shí),假如圓角對(duì)象較多,會(huì)導(dǎo)致GPU負(fù)載大增。這時(shí)候我們可以設(shè)置layer的shouldRasterize屬性為YES,可以將負(fù)載轉(zhuǎn)移給CPU。更為徹底的做法是直接在后臺(tái)繪制圓角圖片然后輸出到主線程顯示,避免使用圓角、陰影、遮罩等屬性。(這種最徹底的做法我沒試過)

3.將GPU的部分渲染轉(zhuǎn)接給CPU,那么如何轉(zhuǎn)接呢?我們可以在單個(gè)控件中重載drawRect:方法,直接將文字和圖片繪制然后輸出到主線程上。

-(void)drawRect:(CGRect)rect{UIImage*image = [UIImageimageNamed:@"logo"];? ? [image drawInRect:CGRectMake(0,0,100,100)];NSString*str =@"123 1234 12345 123456";CGContextRefctx =UIGraphicsGetCurrentContext();CGContextAddRect(ctx,CGRectMake(0,0,100,100));CGContextStrokePath(ctx);? ? [str drawInRect:CGRectMake(0,0,100,100) withAttributes:nil];}

47C81F4F-5E36-4F96-A5F9-8D730A099DD8.png

當(dāng)然你也可以設(shè)置Dictionary給Attributes賦值達(dá)到自己想要的文字效果。這段代碼禁用了一些混合操作,減輕了GPU的負(fù)擔(dān),從而使UITableView滑動(dòng)更加流暢。

總之,性能優(yōu)化要注意平衡CPU和GPU的負(fù)載。


原文鏈接:http://www.itdecent.cn/p/8f3ed86e6480

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、tableview通用工作流程 一個(gè)tableView需要顯示內(nèi)容的時(shí)候,首先會(huì)發(fā)送網(wǎng)絡(luò)請(qǐng)求,向服務(wù)器請(qǐng)求數(shù)據(jù)...
    強(qiáng)降雨天氣閱讀 904評(píng)論 0 5
  • UITableViewCell 父類是UIView UITableView的每一行都是一個(gè)UITableViewC...
    翻這個(gè)墻閱讀 6,805評(píng)論 0 1
  • 概述在iOS開發(fā)中UITableView可以說是使用最廣泛的控件,我們平時(shí)使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,290評(píng)論 3 38
  • 2015-03-02 拍立行買車助手 想速查車評(píng),了解更多買車指南,砍價(jià)策略, 用車技巧, 和最新最I(lǐng)n車訊的車友...
    拍立行閱讀 180評(píng)論 0 1
  • 作者:東樹 遙望青山遠(yuǎn),波明鷺磧沙。數(shù)聲槐夢(mèng)碎,無語對(duì)年華。牛柳喘平午,綠楊行野斜。荷花堤畔問,沽酒向誰家。
    隨意詩社閱讀 600評(píng)論 1 4

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