UITableView的復(fù)用機(jī)制以及性能優(yōu)化

UITableView的復(fù)用機(jī)制

UITableView首先加載一屏幕(假設(shè)UITableView的大小是整個(gè)屏幕的大小)所需要的UITableViewCell,具體個(gè)數(shù)要根據(jù)每個(gè)cell的高度而定,總之肯定要鋪滿整個(gè)屏幕,更準(zhǔn)確說(shuō)當(dāng)前加載的cell的高度要大于屏幕高度蛮位。然后你往上滑動(dòng)垛孔,想要查看更多的內(nèi)容,那么肯定需要一個(gè)新的cell放在已經(jīng)存在內(nèi)容的下邊譬正。這時(shí)候先不去生成簇捍,而是先去UITableView自己的一個(gè)資源池里去獲取划滋。這個(gè)資源池里放了已經(jīng)生成的而且能用的cell。如果資源池是空的話才會(huì)主動(dòng)生成一個(gè)新的cell腰湾。那么這個(gè)資源池里的cell又來(lái)自哪里呢雷恃?當(dāng)你滑動(dòng)時(shí)視圖是,位于最頂部的cell會(huì)相應(yīng)的往上滑動(dòng)费坊,直到它徹底消失在屏幕上倒槐,消失的cell去了哪里呢?你肯定想到了附井,是的讨越,它被UITableView放到資源池里了两残。其他cell也是這樣,只要一滑出屏幕就放入資源池把跨。這樣人弓,有進(jìn)有出,總共需要大約一屏幕多一兩個(gè)的cell就夠了着逐。相對(duì)于1000來(lái)說(shuō)節(jié)省的資源就是指數(shù)級(jí)啊崔赌,完美解決了性能問(wèn)題。

常用的代碼

//方法1
- (void)viewDidLoad { 
    [super viewDidLoad]; 
        // Setup table view. 
    self.myTableView.delegate = self;
    self.myTableView.dataSource = self;
   [self.myTableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:@"MyTableViewCell"];
   //或者[UITableView registerNib:forCellReuseIdentifier:]方法
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     static NSString *CellIdentifier = @"MyTableViewCell"; 
     UITableViewCell *cell = nil; 
     cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    //do something
    return cell;
}
//方法2
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    static NSString *CellIdentifier = @"UITableViewCell"; 
   UITableViewCell *cell = nil; 
   cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
   if (!cell) { 
       cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
      //不要在這里設(shè)置cell的屬性
   } 
   //do something
   return cell;
}

注意這里- [UITableView registerClass:forCellReuseIdentifier:]- [UITableView registerNib:forCellReuseIdentifier:]這兩個(gè)方法一定不能用錯(cuò)耸别,否則就會(huì)報(bào)錯(cuò)健芭。
還有就是我注釋中說(shuō)的不要在if里面設(shè)置cell的屬性,這就是因?yàn)樗膹?fù)用機(jī)制秀姐,如果在那里設(shè)置了慈迈,你后面的cell因?yàn)槭菑?fù)用的前面的cell,所以不會(huì)執(zhí)行if里面的代碼囊扳,就會(huì)導(dǎo)致你設(shè)置的屬性失效吩翻。

UITableView的優(yōu)化

1兜看、cell高度的計(jì)算

如果是固定高度锥咸,則直接設(shè)置self.tableView.rowHeight = 88;,不要重寫-(CGFloat)tableView:(UITableView *)tableViewheightForRowAtIndexPath:(NSIndexPath *)indexPath這個(gè)方法细移,重寫了這個(gè)方法后前面設(shè)置的rowHeight將會(huì)失效搏予,并且每次顯示一個(gè)cell都會(huì)調(diào)用一次這個(gè)方法,所以重寫這個(gè)方法肯定沒(méi)有直接設(shè)置rowHeight效率高弧轧。
iOS8之后有了self-sizing cell的概念雪侥,cell可以自己算出高度,使用self-sizing cell需要滿足以下三個(gè)條件:

  • 使用Autolayout進(jìn)行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關(guān)系)精绎。
  • 指定TableView的estimatedRowHeight屬性的默認(rèn)值速缨,就是初始化的一個(gè)默認(rèn)高度。
  • 指定TableView的rowHeight屬性為UITableViewAutomaticDimension代乃。
- (void)viewDidload {
    self.myTableView.estimatedRowHeight = 44.0;
    self.myTableView.rowHeight = UITableViewAutomaticDimension;
}

除了提高cell高度的計(jì)算效率之外旬牲,對(duì)于已經(jīng)計(jì)算出的高度,也可以進(jìn)行緩存搁吓,對(duì)于已經(jīng)計(jì)算過(guò)的高度原茅,沒(méi)有必要進(jìn)行計(jì)算第二次。

2堕仔、渲染

GPU渲染機(jī)制:

CPU 計(jì)算好顯示內(nèi)容提交到 GPU擂橘,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū),隨后視頻控制器會(huì)按照 VSync 信號(hào)逐行讀取幀緩沖區(qū)的數(shù)據(jù)摩骨,經(jīng)過(guò)可能的數(shù)模轉(zhuǎn)換傳遞給顯示器顯示通贞。

GPU屏幕渲染有以下兩種方式:
  • On-Screen Rendering
    意為當(dāng)前屏幕渲染朗若,指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)中進(jìn)行。
  • Off-Screen Rendering
    意為離屏渲染昌罩,指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開(kāi)辟一個(gè)緩沖區(qū)進(jìn)行渲染操作捡偏。
離屏渲染的代價(jià)

相比于當(dāng)前屏幕渲染,離屏渲染的代價(jià)是很高的峡迷,主要體現(xiàn)在兩個(gè)方面:

  • 創(chuàng)建新緩沖區(qū)

要想進(jìn)行離屏渲染银伟,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)。

  • 上下文切換

離屏渲染的整個(gè)過(guò)程绘搞,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen)彤避;等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕夯辖。而上下文環(huán)境的切換是要付出很大代價(jià)的琉预。

總之:離屏渲染會(huì)付出很大的開(kāi)銷,能避免離屏渲染盡量就不要離屏渲染

下面的情況或操作會(huì)引發(fā)離屏渲染:
  • 設(shè)置透明(alpha)屬性
  • 為圖層設(shè)置遮罩(layer.mask)
  • 將圖層的layer.masksToBounds / view.clipsToBounds屬性設(shè)置為true
  • 將圖層layer.allowsGroupOpacity屬性設(shè)置為YES和layer.opacity小于1.0
  • 為圖層設(shè)置陰影(layer.shadow *)蒿褂。
  • 為圖層設(shè)置layer.shouldRasterize=true
  • 具有l(wèi)ayer.cornerRadius圆米,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層
  • 文本(任何種類啄栓,包括UILabel娄帖,CATextLayer,Core Text等)使用CGContext在drawRect :方法中繪制大部分情況下會(huì)導(dǎo)致離屏渲染昙楚,甚至僅僅是一個(gè)空的實(shí)現(xiàn)

iOS 9.0 之前UIimageView跟UIButton設(shè)置圓角都會(huì)觸發(fā)離屏渲染近速。
iOS 9.0 之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了堪旧,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的削葱。

常用的幾個(gè)優(yōu)化
1、圓角優(yōu)化

在APP開(kāi)發(fā)中淳梦,圓角圖片還是經(jīng)常出現(xiàn)的析砸。如果一個(gè)界面中只有少量圓角圖片或許對(duì)性能沒(méi)有非常大的影響,但是當(dāng)圓角圖片比較多的時(shí)候就會(huì)APP性能產(chǎn)生明顯的影響爆袍。
我們?cè)O(shè)置圓角一般通過(guò)如下方式:

imageView.layer.cornerRadius=CGFloat(10);
imageView.layer.masksToBounds=YES;

優(yōu)化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角

UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; 
imageView.image = [UIImage imageNamed:@"myImg"]; 
//開(kāi)始對(duì)imageView進(jìn)行畫圖 
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0); 
//使用貝塞爾曲線畫出一個(gè)圓形圖 
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width/2.0] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext(); 
//結(jié)束畫圖 
UIGraphicsEndImageContext();
[self.view addSubview:imageView];

優(yōu)化方案2:使用CAShapeLayer和UIBezierPath設(shè)置圓角

UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
//設(shè)置大小
maskLayer.frame=imageView.bounds;
//設(shè)置圖形樣子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.view addSubview:imageView];

對(duì)于方案2的解釋:

  • 使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實(shí)現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
  • CAShapeLayer動(dòng)畫渲染直接提交到手機(jī)的GPU當(dāng)中首繁,相較于view的drawRect方法使用CPU渲染而言,其效率極高螃宙,能大大優(yōu)化內(nèi)存使用情況蛮瞄。

總的來(lái)說(shuō)就是用CAShapeLayer的內(nèi)存消耗少,渲染速度快谆扎,建議使用優(yōu)化方案2挂捅。

2、shadow優(yōu)化

對(duì)于shadow堂湖,如果圖層是個(gè)簡(jiǎn)單的幾何圖形或者圓角圖形闲先,我們可以通過(guò)設(shè)置shadowPath來(lái)優(yōu)化性能状土,能大幅提高性能。示例如下:

imageView.layer.shadowColor=[UIColor grayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.bounds];
imageView.layer.shadowPath=path.CGPath;

我們還可以通過(guò)設(shè)置shouldRasterize屬性值為YES來(lái)強(qiáng)制開(kāi)啟離屏渲染伺糠。其實(shí)就是光柵化(Rasterization)蒙谓。既然離屏渲染這么不好,為什么我們還要強(qiáng)制開(kāi)啟呢训桶?當(dāng)一個(gè)圖像混合了多個(gè)圖層累驮,每次移動(dòng)時(shí),每一幀都要重新合成這些圖層舵揭,十分消耗性能谤专。當(dāng)我們開(kāi)啟光柵化后,會(huì)在首次渲染的時(shí)候產(chǎn)生一個(gè)位圖緩存午绳,當(dāng)再次使用時(shí)候就會(huì)復(fù)用這個(gè)緩存置侍。但是如果圖層發(fā)生改變的時(shí)候就會(huì)重新產(chǎn)生位圖緩存。所以這個(gè)功能一般不能用于UITableViewCell中拦焚,cell的復(fù)用反而降低了性能蜡坊。最好用于圖層較多的靜態(tài)內(nèi)容的圖形。而且產(chǎn)生的位圖緩存的大小是有限制的赎败,一般是2.5個(gè)屏幕尺寸秕衙。在100ms之內(nèi)不使用這個(gè)緩存,緩存也會(huì)被刪除螟够。所以我們要根據(jù)使用場(chǎng)景而定灾梦。

3峡钓、其他的一些優(yōu)化建議
  • 當(dāng)我們需要圓角效果時(shí)妓笙,可以使用一張中間透明圖片蒙上去
  • 使用ShadowPath指定layer陰影效果路徑
  • 使用異步進(jìn)行l(wèi)ayer渲染(Facebook開(kāi)源的異步繪制框架AsyncDisplayKit)
  • 設(shè)置layer的opaque值為YES,減少?gòu)?fù)雜圖層合成(如果opaque設(shè)置NO能岩,那么Alpha應(yīng)該小于1)
  • 盡量使用不包含透明(alpha)通道的圖片資源
  • 盡量設(shè)置layer的大小值為整形值
  • 直接讓美工把圖片切成圓角進(jìn)行顯示寞宫,這是效率最高的一種方案
  • 很多情況下用戶上傳圖片進(jìn)行顯示,可以讓服務(wù)端處理圓角
  • 使用代碼手動(dòng)生成圓角Image設(shè)置到要顯示的View上拉鹃,利用UIBezierPath(CoreGraphics框架)畫出來(lái)圓角圖片

3辈赋、其它

  • 1) 減少視圖的數(shù)目:
    我們?cè)赾ell上添加系統(tǒng)控件的時(shí)候,實(shí)際上系統(tǒng)都會(huì)調(diào)用底層的接口進(jìn)行繪制膏燕,大量添加控件時(shí)钥屈,會(huì)消耗很大的資源并且也會(huì)影響渲染的性能。當(dāng)使用默認(rèn)的UITableViewCell并且在它的ContentView上面添加控件時(shí)會(huì)相當(dāng)消耗性能坝辫。所以目前最佳的方法還是繼承UITableViewCell篷就,并重寫drawRect方法,并且這里的繪制過(guò)程可以通過(guò)多線程異步繪制近忙。
  • 2)減少多余的繪制操作:
    在實(shí)現(xiàn)drawRect方法的時(shí)候竭业,它的參數(shù)rect就是我們需要繪制的區(qū)域智润,在rect范圍之外的區(qū)域我們不需要進(jìn)行繪制,否則會(huì)消耗相當(dāng)大的資源未辆。
  • 3)不要給cell動(dòng)態(tài)添加subView:
    在初始化cell的時(shí)候就將所有需要展示的添加完畢窟绷,然后根據(jù)需要來(lái)設(shè)置hide屬性顯示和隱藏。
  • 4)滑動(dòng)時(shí)按需加載對(duì)應(yīng)的內(nèi)容:
    滑動(dòng)很快時(shí)咐柜,只加載目標(biāo)范圍內(nèi)的cell兼蜈,這樣按需加載(配合SDWebImage),極大提高流暢度拙友,但是這樣在滑動(dòng)過(guò)程中就會(huì)暫時(shí)顯示空白饭尝,這就需要在性能和用戶體驗(yàn)中權(quán)衡了。

最后安利一篇YY大神的博客
YY大神:iOS 保持界面流暢的技巧.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末献宫,一起剝皮案震驚了整個(gè)濱河市钥平,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姊途,老刑警劉巖涉瘾,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捷兰,居然都是意外死亡立叛,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門贡茅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秘蛇,“玉大人,你說(shuō)我怎么就攤上這事顶考×藁梗” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵驹沿,是天一觀的道長(zhǎng)艘策。 經(jīng)常有香客問(wèn)我,道長(zhǎng)渊季,這世上最難降的妖魔是什么朋蔫? 我笑而不...
    開(kāi)封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮却汉,結(jié)果婚禮上驯妄,老公的妹妹穿的比我還像新娘。我一直安慰自己合砂,他們只是感情好青扔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般赎懦。 火紅的嫁衣襯著肌膚如雪雀鹃。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天励两,我揣著相機(jī)與錄音黎茎,去河邊找鬼。 笑死当悔,一個(gè)胖子當(dāng)著我的面吹牛傅瞻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盲憎,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嗅骄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了饼疙?” 一聲冷哼從身側(cè)響起溺森,我...
    開(kāi)封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窑眯,沒(méi)想到半個(gè)月后屏积,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磅甩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年炊林,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卷要。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渣聚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出僧叉,到底是詐尸還是另有隱情奕枝,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布彪标,位于F島的核電站倍权,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捞烟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一当船、第九天 我趴在偏房一處隱蔽的房頂上張望题画。 院中可真熱鬧,春花似錦德频、人聲如沸苍息。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竞思。三九已至表谊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盖喷,已是汗流浹背爆办。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留课梳,地道東北人距辆。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像暮刃,于是被迫代替她去往敵國(guó)和親跨算。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容