TableView相信只要是做iOS開發(fā)的就不會陌生,目前大多數(shù)iOS的app都是采用TabBar+NavigationBar+TableViewController這一主流框架,既然用的這么頻繁,肯定就會在開發(fā)過程中碰到一些問題--比如屏幕掉幀、卡頓等現(xiàn)象。這些現(xiàn)象大幅度的降低了用戶的性能體驗,并提高了crash的頻率。因此如何能優(yōu)化好tableView就非??简灣绦蛟硞兊墓Φ琢?。
本猿~啊呸,本人就在開發(fā)公司項目的時候遇到這類問題,當(dāng)快速滑動tableView并且cell中有大量圖片和其他控件需要加載時,就會出現(xiàn)嚴(yán)重掉幀(我們公司的項目當(dāng)時大量采用xib現(xiàn)在逐漸用手寫代碼代替),有時還會crash。由于當(dāng)時項目比較趕進度,所以沒有時間去優(yōu)化性能,這種情況直到功能基本完善為止,花了大量功夫進行性能優(yōu)化。
接下來我會根據(jù)tableView的delegate以及dataSource方法的執(zhí)行順序進行一步一步的講解。
首先當(dāng)一個tableView需要顯示內(nèi)容的時候,首先會發(fā)送網(wǎng)絡(luò)請求,向服務(wù)器請求數(shù)據(jù),然后將數(shù)據(jù)轉(zhuǎn)為我們可以使用的model后進行reload操作,接下來會向delegate和dataSource請求數(shù)據(jù)。這時候會先調(diào)用- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section這個方法(假設(shè)section為1)。根據(jù)model獲取cell的行數(shù)然后調(diào)用-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath,根據(jù)model計算出cell的高度。由于tableView是繼承自scrollView,所以tableView也會有contentSize屬性。它的contentSize取決于所有cell的高度和。和scrollView有一點不同,tableView只會管理可視的cell高度,這樣做的目的是避免不必要的性能開銷。
大多數(shù)情況下我們是將model直接傳給cell然后在cell里進行計算各控件的相對位置(利用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:@""];? ? }}
但是這樣做假如滑動比較快,且內(nèi)部控件比較復(fù)雜會導(dǎo)致CPU的計算量過大,從而導(dǎo)致掉幀??隙〞腥艘苫鬄槭裁磿魩@是因為GPU 一個機制叫做垂直同步(簡寫也是 V-Sync),當(dāng)開啟垂直同步后,GPU 會等待顯示器的 VSync 信號發(fā)出后,才進行新的一幀渲染和緩沖區(qū)更新。這樣能解決畫面撕裂現(xiàn)象,也增加了畫面流暢度,但需要消費更多的計算資源,也會帶來部分延遲。當(dāng)GPU發(fā)出垂直同步即VSync信號后,CPU開始進行內(nèi)部控件的創(chuàng)建、布局、解碼和控件的相對位置計算。然后將計算好的內(nèi)容交給GPU進行變換、合成、渲染。然后等待下一個VSync信號。(這段理論部分來自于YY大神)假如在VSync信號發(fā)出后,CPU進行計算的時間過長,或者GPU進行渲染的時間過長導(dǎo)致兩段時間加起來超過了1個VSync周期,就會將這一幀動畫丟棄,并維持上一幀的畫面從而導(dǎo)致掉幀。
那么我們?nèi)绾芜M行優(yōu)化呢?
最終目的:平衡CPU和GPU的壓力。正確地利用了CPU和GPU資源,使它們均勻地負(fù)載,這樣子做FPS會保持在60幀。避免出現(xiàn)CPU滿載GPU低負(fù)載或者GPU滿載CPU低負(fù)載的情況。
如何避免出現(xiàn)CPU滿載GPU低負(fù)載呢?
1.不要用AutoLayout,不要用AutoLayout,不要用AutoLayout(這里的情景是子視圖較多的情況下),重要的事情說3遍。我們進行手動布局可能會沒那么方便,但是通過簡單的加減乘除就可以獲取控件相對位置和cell的高度。盡管蘋果推薦使用AutoLayout。但是對于那些比較古老的設(shè)備比如我的5S,CPU通過AutoLayout計算布局會比較吃力,尤其是cell內(nèi)部的控件數(shù)量較多的時候。使用的子視圖越多,AutoLayout的效率越低,這是事實。那么為什么AutoLayout相對低效呢。是因為它要根據(jù)底層“Cassowary”的約束求解系統(tǒng)進行約束計算,從而得到一個唯一解,這時AutoLayout才不會報警告或錯誤(相信拖控件的同學(xué)肯定遇到過各種黃色警告和紅包約束沖突吧)。假如內(nèi)部的子控件越多,它需要進行的線性或非線性計算量越大,需要求解的約束越多,CPU計算耗費大量時間從而導(dǎo)致超過了一個VSync周期。相反的,假如我們進行手動布局,都是非常簡單的線性計算,CPU就不用浪費那么時間,CPU的壓力不會很大,從而平衡了CPU的負(fù)載。
tips :我們可以在tableView進行網(wǎng)絡(luò)請求成功后立刻進行后臺的布局計算。比如是我,利用AFN請求數(shù)據(jù)成功后會在success 的block里面進行后臺的計算:
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(), ^{//這里面把計算好的frameModel返回給主線程并執(zhí)行正常的操作});- (NSMutableArray*)modelFramesWithModelAry:(NSArray*)modelArray{NSMutableArray*frameModels = [NSMutableArrayarray];for(ImageCellModel *modelinmodelArray) {? ? ? ? HomeModelFrame *modelFrame = [[HomeModelFrame alloc] init];? ? ? ? modelFrame.model= model;? ? ? ? [frameModels addObject:modelFrame];? ? }returnframeModels;}
上面代碼會將請求到的JSON數(shù)組轉(zhuǎn)換為model數(shù)組,然后將model數(shù)組里的model轉(zhuǎn)換為modelFrame(就是根據(jù)model的各個屬性計算出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;}
提前計算后各個控件的frame并把cell的高度提前緩存起來等到調(diào)用-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath時直接return _cellHeight,沒有任何計算量,從而減輕CPU的負(fù)載。
假如想進一步優(yōu)化,可以嘗試調(diào)用控件的view.layer.displaysAsynchronously屬性為YES。
之前我們調(diào)用了dataSource和delegate的兩個關(guān)于row和height的方法,接下來tableView會調(diào)用- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath這個返回創(chuàng)建并返回特定row的cell。在這個方法內(nèi)部,我們對cell里的每個控件進行賦值并計算各個控件的布局。只需要把各個控件的frame指向之前已經(jīng)計算好的各個modelFrame就可以了,不需要進行多余的布局計算,然后對每個控件的內(nèi)容進行一一賦值,這樣子就能在調(diào)用cellForRowAtIndexPath這個方法的時候迅速的返回一個cell。
如何避免出現(xiàn)GPU滿載CPU低負(fù)載呢?
1.當(dāng)多個視圖重疊時,GPU會對其進行合成渲染,而渲染最慢的操作之一是混合,因此當(dāng)視圖結(jié)構(gòu)太復(fù)雜就會消耗大量GPU資源,所以當(dāng)一個控件本身是不透明的,注意設(shè)定opaque = YES,這樣子可以避免無用的alpha通道合成,降低GPU負(fù)載。
2.對控件設(shè)置cornerRadius后對其進行clip或mask操作時,會導(dǎo)致offscreen rendering,而這個是在GPU中進行的,所以快速滑動tableView時,假如圓角對象較多,會導(dǎo)致GPU負(fù)載大增。這時候我們可以設(shè)置layer的shouldRasterize屬性為YES,可以將負(fù)載轉(zhuǎn)移給CPU。更為徹底的做法是直接在后臺繪制圓角圖片然后輸出到主線程顯示,避免使用圓角、陰影、遮罩等屬性。(這種最徹底的做法我沒試過)
3.將GPU的部分渲染轉(zhuǎn)接給CPU,那么如何轉(zhuǎn)接呢?我們可以在單個控件中重載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賦值達到自己想要的文字效果。這段代碼禁用了一些混合操作,減輕了GPU的負(fù)擔(dān),從而使UITableView滑動更加流暢。
總之,性能優(yōu)化要注意平衡CPU和GPU的負(fù)載。
原文鏈接:http://m.itdecent.cn/p/8f3ed86e6480