【iOS】性能優(yōu)化總結(jié) —— UITableView

優(yōu)化一、善用重用標(biāo)識(shí)

這個(gè)屬于基礎(chǔ)知識(shí)范疇,就不再過(guò)度的講解了。只需了解使用 static 修飾重用標(biāo)識(shí)名稱能夠保證這個(gè)標(biāo)識(shí)只會(huì)創(chuàng)建一次陨溅,提高性能。接著就是調(diào)用dequeueReusableCellWithIdentifier:方法獲取緩存池中的Cell绍在。如果沒(méi)有就調(diào)用 initWithStyle:ReusIdentifier:方法創(chuàng)建一個(gè)新的Cell门扇。注意事先需要調(diào)用registerNib/registerClass方法為T(mén)ableView注冊(cè)一下重用標(biāo)識(shí)。

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];  
    if (!cell) {  
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];  
    } 

需要注意的是我們?cè)谧远xCell的時(shí)候需要盡量將相識(shí)度高的Cell合并為一種樣式的Cell偿渡。我們知道運(yùn)行在當(dāng)前設(shè)備屏幕中的Cell數(shù)量是有限的臼寄,設(shè)為N個(gè)。如果有M種樣式溜宽,那么基于Cell的重用機(jī)制吉拳,我們知道在緩存池中運(yùn)行最多時(shí)將有M*N個(gè)Cell的實(shí)例被創(chuàng)建,這顯然會(huì)占用大量的內(nèi)存适揉。

優(yōu)化二留攒、設(shè)置預(yù)估行高煤惩,預(yù)先緩存動(dòng)態(tài)行高

1. 設(shè)置預(yù)估行高

我們知道UITableView是通過(guò)設(shè)置UITableView代理方法heightForRowAtIndexPath:方法來(lái)設(shè)置行高。自從iOS8.0之后炼邀,蘋(píng)果新增了self-sizing cell的概念魄揉,也是cell可以自己計(jì)算行高,使用需要滿足三個(gè)條件:
(1) 使用Autolayout進(jìn)行UI布局約束
(2) 指定TableView的estimatedRowHeight屬性的默認(rèn)值
(3) 指定TableView的rowHeight的屬性為UITableViewAutomaticDimension拭宁。

TableView在加載數(shù)據(jù)時(shí)會(huì)先通過(guò)estimatedHeightForRowAtIndexPath處理全部數(shù)據(jù)洛退,此時(shí)我們只需要提供一個(gè)粗略的高度,待到cell對(duì)象創(chuàng)建之后再去設(shè)置cell的真實(shí)高度杰标。而且只會(huì)處理當(dāng)前屏幕范圍內(nèi)的cell兵怯,這樣子會(huì)顯著的提升加載的性能。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {  
    return 50.0;  
}  
  
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {  
    return 30.0;  
}  
2. 預(yù)先計(jì)算并緩存行高

自從iOS8.0之后在旱,TableView的數(shù)據(jù)源的調(diào)用時(shí)序也發(fā)生了變化摇零,下圖左邊為iOS7.0及之前的時(shí)序,右邊為iOS8.0及之后的時(shí)序:

9073B542-D111-4D3C-A89F-B8AB87B4EF27.png

從上圖我們可以很容易的分析出桶蝎,iOS8.0之后再獲取cell對(duì)象之后會(huì)再次調(diào)用heightForRowAtIndexPath: 方法獲取行高,這也就意味著我們其實(shí)可以先創(chuàng)建cell對(duì)象谅畅,之后再提供行高登渣。具體方法我們可以在cell類中添加layoutAttribute屬性,記錄相應(yīng)的UIEdgeInsets毡泻,然后在設(shè)置cell真實(shí)高度的時(shí)候返回胜茧。iOS7.0之前則是必須在cell對(duì)象創(chuàng)建之前先獲得所有Cell的高度。

優(yōu)化三仇味、減少Subviews層級(jí)呻顽、異步繪制、避免離屛渲染丹墨、使用hidden隱藏圖層

1. 減少圖層層級(jí)數(shù)

當(dāng)我們自定義了某個(gè)cell廊遍,并在cell上添加大量的系統(tǒng)控件后,在創(chuàng)建該cell對(duì)象時(shí)系統(tǒng)會(huì)調(diào)用底層接口進(jìn)行繪制贩挣,大量的添加操作會(huì)消耗很大的資源同時(shí)會(huì)影響渲染的性能喉前。

2. 異步繪制

解決因圖層層級(jí)多造成的性能問(wèn)題,我們可以通過(guò)重寫(xiě)drawReact:方法王财,調(diào)用Core Graphics框架中的API進(jìn)行異步繪制卵迂,提高效率。drawRect:本身是異步的绒净。另外drawRect:中大量的繪制操作也會(huì)造成內(nèi)存的增長(zhǎng)见咒,可以使用CAShapeLayer來(lái)代替。

3. 減少多于的繪制操作

在實(shí)現(xiàn)drawRect:方法的時(shí)候挂疆,他的參數(shù)rect就是我們需要繪制的區(qū)域改览,在rect范圍之外的區(qū)域不要繪制哎垦,否則會(huì)消耗相當(dāng)大的資源。

4. 圖片加載時(shí)機(jī)選擇

首先在Cell類中添加圖片應(yīng)該避免使用imageWithName:方法恃疯,因?yàn)樵摲椒〞?huì)將圖片緩存到內(nèi)存中漏设。而是應(yīng)該使用imageWithContensOfFile:方法來(lái)替換,該方法在圖片使用完后系統(tǒng)會(huì)自動(dòng)釋放資源今妄,并不會(huì)緩存下來(lái)郑口。另外結(jié)合SDWebImage框架的使用可以顯著的提高圖片加載的性能。

5. 避免動(dòng)態(tài)添加圖層

在cell中應(yīng)該盡量避免動(dòng)態(tài)創(chuàng)建圖層盾鳞。在初始化cell的時(shí)候一并將所有圖層預(yù)先創(chuàng)建好犬性,通過(guò)hidden屬性控制子圖層的顯示或隱藏,因?yàn)閱渭兊娘@示操作要比創(chuàng)建快的多腾仅。

6. 避免離屛渲染
  • 為圖層設(shè)置遮罩(layer.mask
  • 設(shè)置圖層的 layer.masksToBounds/view.clipsToBounds屬性為T(mén)rue
  • 設(shè)置圖層的 layer.allowsGroupOpacity的屬性為T(mén)rue和layer.opacity小于1.0
  • 設(shè)置圖層陰影(layer.shadow
  • 設(shè)置圖層的 layer.shouldRasterize的屬性為T(mén)rue
  • 具有 layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsAntialiasing的圖層
  • 文本(任何種類乒裆,包括UILabelCATextLayer推励、Core Text等)
  • 使用CGContextdrawReact:方法中繪制
    上述情況均會(huì)造成離屛渲染鹤耍。

什么是離屛渲染?我們知道iOS底層的渲染框架使用的是OpenGL ES验辞。OpenGL中稿黄,GPU渲染屏幕方式有兩種:當(dāng)前屏幕渲染(On-Screen Rendering)和離屛渲染(Off-Screen Rendering)。它們的區(qū)別是當(dāng)前屏幕渲染操作是在當(dāng)前顯示的屏幕緩沖區(qū)完成跌造,而離屛渲染會(huì)在另外一個(gè)新開(kāi)辟的緩沖區(qū)完成渲染操作杆怕。開(kāi)啟離屛渲染的代價(jià)就是需要新開(kāi)辟一塊新的緩沖區(qū),在渲染的過(guò)程中還會(huì)多次的切換上下文壳贪,這些都是很消耗性能的陵珍。

7. 圖片圓角優(yōu)化
  • 使用貝塞爾曲線 + Core Graphics框架設(shè)置圓角
- (void)setImageCircularEdge:(UIImageView *)imageView {  
      
    //開(kāi)始對(duì)imageView進(jìn)行畫(huà)圖  
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);  
    //使用貝塞爾曲線畫(huà)出一個(gè)圓形圖  
    [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];  
    [imageView drawRect:imageView.bounds];  
    imageView.image = UIGraphicsGetImageFromCurrentImageContext();  
    //結(jié)束畫(huà)圖  
    UIGraphicsEndImageContext();  
}  
  • 使用貝塞爾曲線 + CAShapeLayer 設(shè)置圓角
- (void)setImageCircularEdge2:(UIImageView *)imageView {  
  
    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;  
}  
8. 圖片陰影優(yōu)化
- (void)setImageShadow:(UIImageView *)imageView {  
      
    imageView.layer.shadowColor = [UIColor grayColor].CGColor;  
    imageView.layer.shadowOpacity = 1.0;  
    imageView.layer.shadowRadius = 2.0;  
    UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.frame];  
    imageView.layer.shadowPath = path.CGPath;  
}  

優(yōu)化四、分屏加載數(shù)據(jù)违施,預(yù)先異步請(qǐng)求數(shù)據(jù)

在我們的項(xiàng)目開(kāi)發(fā)中列表視圖的應(yīng)用很多互纯,有時(shí)數(shù)據(jù)比較多的時(shí)候我們不可能一次加載所有數(shù)據(jù),這樣子會(huì)導(dǎo)致內(nèi)存的暴漲醉拓,同時(shí)用戶不一定會(huì)瀏覽完所有的信息伟姐,造成資源浪費(fèi)。這時(shí)我們可以通過(guò)分屏加載來(lái)解決這個(gè)問(wèn)題亿卤,比如第一次加載10條數(shù)據(jù)愤兵,當(dāng)我向上滑動(dòng)列表的時(shí)候通常我們會(huì)再次去請(qǐng)求數(shù)據(jù)接口獲取下一個(gè)10條數(shù)據(jù)。這個(gè)時(shí)候如果我們不做任何的處理排吴,那么我們會(huì)發(fā)現(xiàn)每次劃過(guò)10條數(shù)據(jù)的時(shí)候列表都需要停頓一下秆乳,等待數(shù)據(jù)加載。這樣子我們的列表就表現(xiàn)的不是很流暢了,怎么解決這個(gè)問(wèn)題呢屹堰?

提前異步預(yù)加載數(shù)據(jù)肛冶!第一次加載完10條數(shù)據(jù)之后我可以再預(yù)先加載下10條數(shù)據(jù),當(dāng)劃過(guò)第10條數(shù)據(jù)時(shí)扯键,我再請(qǐng)求下下10條數(shù)據(jù)睦袖。這樣子我們的列表就表現(xiàn)的很流暢了。

優(yōu)化五荣刑、滑動(dòng)TableView時(shí)馅笙,按需加載內(nèi)容

有些情況下我們可能會(huì)去快速的滑動(dòng)列表,這時(shí)候其實(shí)會(huì)有大量的cell對(duì)象被創(chuàng)建厉亏、被重用董习,其實(shí)我們可能只是去瀏覽列表停止的那一頁(yè)的上下一定范圍內(nèi)的信息,前面快速劃過(guò)的那些信息對(duì)我們來(lái)說(shuō)都是無(wú)用的爱只。有什么方法讓我們只去加載我最后那頁(yè)的目標(biāo)范圍內(nèi)的列表數(shù)據(jù)呢皿淋?那就是通過(guò)ScrollView的代理方法scrollViewWillEndDragging:withVelocity:targetContentoffset:來(lái)實(shí)現(xiàn)的。

#pragma mark - UIScrollViewDelegate  
//按需加載 - 如果目標(biāo)行與當(dāng)前行相差超過(guò)指定行數(shù)恬试,只在目標(biāo)滾動(dòng)范圍的前后指定3行加載窝趣。  
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {  
      
    NSIndexPath *targetPath = [_myTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];  
    NSIndexPath *firstVisiblePath = [[_myTableView indexPathsForVisibleRows] firstObject];  
    NSInteger skipCount = 8;  
    if (labs(firstVisiblePath.row - targetPath.row)>  skipCount) {  
        NSArray *temp = [_myTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, _myTableView.frame.size.width, _myTableView.frame.size.height)];  
        NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];  
        if (velocity.y<0) {  
            NSIndexPath *indexPath = [temp lastObject];  
            if (indexPath.row+33) {  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];  
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];  
            }  
        }  
        [_dataList addObjectsFromArray:arr];  
    }  
}  

targetContentOffset 是TableView減速到停止的地方, velocity 表示速度向量。

優(yōu)化六忘渔、Cell類中應(yīng)該避免請(qǐng)求網(wǎng)絡(luò)加載數(shù)據(jù)

如果確實(shí)有需求不可避免高帖,可以將網(wǎng)絡(luò)加載任務(wù)添加到Runloop中,設(shè)置DefaultRunloopModule模式畦粮。這樣子可以起到延遲加載的作用。

優(yōu)化七乖阵、在willDisplayCell:forRowAtIndexPath:代理方法中綁定數(shù)據(jù)

初學(xué)iOS的時(shí)候宣赔,各類教程以及書(shū)籍中都喜歡在cellForRowAtIndexPath:方法中綁定數(shù)據(jù),然后此時(shí)的Cell其實(shí)還未顯示瞪浸,該方法中包含了大量的布局儒将、繪制相關(guān)的操作。我們應(yīng)該在該方法中盡量簡(jiǎn)化我們自身的邏輯操作对蒲。這時(shí)我們可以使用在willDisplayCell:forRowAtIndePath:方法中綁定數(shù)據(jù)钩蚊。

#pragma mark - UITableViewDataSource  
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
    static NSString *cellIdentifier = @"MyTableViewCell";  
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];  
    if (!cell) {  
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];  
    }  
      
    return cell;  
}  
  
#pragma mark - UITableViewDelegate  
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {  
      
    NSDictionary *dict = self.dataList[indexPath];  
    [cell updateData:dict];  
}  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蹈矮,隨后出現(xiàn)的幾起案子砰逻,更是在濱河造成了極大的恐慌,老刑警劉巖泛鸟,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝠咆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)刚操,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)闸翅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人菊霜,你說(shuō)我怎么就攤上這事坚冀。” “怎么了鉴逞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵记某,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我华蜒,道長(zhǎng)辙纬,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任叭喜,我火速辦了婚禮贺拣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捂蕴。我一直安慰自己譬涡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布啥辨。 她就那樣靜靜地躺著涡匀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溉知。 梳的紋絲不亂的頭發(fā)上陨瘩,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音级乍,去河邊找鬼舌劳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛玫荣,可吹牛的內(nèi)容都是我干的甚淡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捅厂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贯卦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起焙贷,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤撵割,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盈厘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體睁枕,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了外遇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片注簿。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖跳仿,靈堂內(nèi)的尸體忽然破棺而出诡渴,到底是詐尸還是另有隱情,我是刑警寧澤菲语,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布妄辩,位于F島的核電站,受9級(jí)特大地震影響山上,放射性物質(zhì)發(fā)生泄漏眼耀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一佩憾、第九天 我趴在偏房一處隱蔽的房頂上張望哮伟。 院中可真熱鬧,春花似錦妄帘、人聲如沸楞黄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鬼廓。三九已至,卻和暖如春致盟,著一層夾襖步出監(jiān)牢的瞬間碎税,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工馏锡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚣录,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓眷篇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親荔泳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蕉饼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

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