iOS系列開發-UITableView性能優化

#iOS系列開發-UITableView性能優化ios

在咱們的平常開發中,不少開發人員最常接觸的就是UITableView或者UICollectionView來佈局某些列表等界面. 這裏咱們就拿UITableView來做爲說明內容 絕大部分的時候,一個UITableView的內容不會不少,cell的樣式\高度也不會不少元化,其僅僅做爲一個展現用的UITableView來講,不少時候其性能都是很不錯的. 可是也會有小衆的時候,一個做爲列表展現的界面會有不少不少數據,並且是實時的會加載不少新的內容,表格的樣式也不惟一,有的僅有文字,有的僅有圖片,有的高度很長,在複雜點的不少cell雖然整體上差很少,可是卻會有不少或多或少的佈局上的不同或者組件上的差距git

固然,並非說有了這些複雜的內容,咱們的UITableView就會性能變差,可是咱們卻能夠說性能上比較差的UITableView,不少緣由都是由於這些不定的因素,複雜的邏輯判斷,複雜的數據處理,複雜的圖形渲染,複雜的高度計算等等致使的.算法

並且若是表格的性能真的已經變差了,那麼其調優的步驟是必定須要進行的,不然做爲開發人員,咱們過不了本身的關,過不了測試的關,過不了產品的關...json

可是說白了UITableView也就那麼些方法 咱們可以利用的有哪些?數組

@property (nonatomic) CGFloat rowHeight;
複製代碼

是的,你會發現當rowHeight時惟一固定的時候,每每性能不會太差緩存

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
複製代碼

咱們都知道,當咱們使用了代理的方法以後,上面的行高屬性就會失效了,可是相對應的,咱們須要給每一行都返回一個行高,這個是沒法避免的,那麼咱們可否在這裏作些優化呢? 答案是確定的,並且是顯著的. 爲何這麼說呢? 當咱們的UITableView是一個動態行高的表格的話,咱們以往的算法就是動態計算,什麼意思呢? 大體是這樣的性能優化

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    DetaileModel *model = _listArray[indexPath.row];//取出模型
    return [DetailCell heigthForModel:model];//計算所需高度並返回
}
複製代碼

可是響應的,由於內容的多樣化,可能有,這部分的計算本事雖然不會很耗時,可是由於咱們reloadData的時候或者滾動的時候,其都須要不停的計算高度並返回,這樣就會形成不少的展現表格或者建立或者重用表格cell的時候都須要耗時在計算高度的這部分上,那麼咱們如何調節呢?這部分計算確定是須要的,此時你可能會說,iOS如今支持自適應高度,bash

@property (nonatomic) CGFloat estimatedRowHeight NS_AVAILABLE_IOS(7_0); // default is 0, which means there is no estimate
複製代碼

是的,UITableView中,咱們能夠寫一個預估行高,而後使用xib或者storyboard或者手動layout的時候編寫好約束的時候,便可動態自動的返回行高,但這樣的性能其實更差,時間所有都在調節約束上面了,而後硬生生的依靠約束來調節行高,這樣的性能只會更差,不推薦.網絡

因此咱們能夠採起的是app

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    DetaileModel *model = _listArray[indexPath.row];//取出模型
    return model.cellHeight;//得到所需高度並返回
}
複製代碼

若是咱們把cellHeight當作一個屬性,提早緩存好,以後直接獲取怎麼樣呢? 此時你就會說,這樣有什麼用嗎? 簡單的說幾點 1.使用內存換性能 2.不須要重複計算

爲何這麼說呢? 首先咱們會發現,每每咱們計算行高和設置cell的時候都是根據模型數據來判斷哪些須要顯示哪些不須要顯示,顯示的話顯示的位置和大小分別是多少,咱們都是根據數據模型的內容來判斷的.那麼咱們若是這設置這個模型數據的時候(網絡請求回來json轉模型的時候),咱們就根據這個模型的數據添加一個cellHeight的屬性,而且直接計算好保存在模型中,當作模型的一個字段,那麼咱們在reloadData的時候咱們就會發現咱們把計算高度的事情提早一步作好了,咱們只須要返回model的cellHeight便可.並且由於其存儲在該模型數據中,那麼其跟模型同樣,一直是存在的,咱們便可以在從新刷新表格或者滾動視圖的時候都不須要再一次計算了(只要改模型存在,那麼該cellHeight屬性就一直存在).

咱們會發現僅僅是一個提早計算並存儲起來,一個表格的性能就會大大提升,一些稍微不是特別複雜的表格此時已經就可以很流暢了.

至此咱們會發現咱們使用了相似設置cell的方式

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellid = @"cellid";
    DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];
    if (!cell) {
        cell = [[DetailCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];
    }
    cell.model = _listArray[indexPath.row];
    return cell;
}
複製代碼

不少的開發者都使用相似的方式,在cell中有一個model屬性,或者設置model的方法,以此來經過傳遞一個model來設置數據,可能在自定義的設置方法中,可能在model的setter方法裏面,此時咱們又會一到一個新的問題,某些model自己並不深很複雜,可是咱們須要對這些數據作一些處理以後在作展現,好比圖文混排,好比拼接分割轉換等處理字符的顯示,這些在以往的時候,咱們都是放在設置cell的數據源方法裏面,經過模型的傳遞過來,而後經過進一步的轉化或者修改來決定如何顯示數據. 可是一樣的,此時,咱們就須要不停的在表格更新顯示的時候來計算.

此時你確定會想到,處理方法和上面同樣,提早計算好! 是的,咱們在設置模型的時候就提早計算好,在模型中添加新的字段,存儲這些須要計算的數據,在cell展現的時候直接拿過來用便可.

可是問題來了,新的數據模型彷彿不是數據模型了,參與了不少的計算(內容計算,行高計算...) 那麼咱們能不能優化一下呢? 這是咱們以前的邏輯

這裏寫圖片描述
這是咱們如今的邏輯
這裏寫圖片描述
咱們可以輕鬆的優化了紅色部分的性能了 緣由是咱們把全部的須要提早計算的東西都提早了,將原來的須要計算的部分所有轉化成了一個內存,使用內存來代替以前的計算,在須要的時候只須要從內存中直接拿出來使用便可.優勢上面簡單的說明了,只須要在賦值的時候計算好便可,以後就不須要計算了,之後的刷新都是直接從內存中取出所須要的內容或者行高便可 可是,相對應的,模型就變的不在像模型了,(此時的模型中有了計算行高的部分,有了計算新的內容的部分),而且跟網絡返回回來的模型已經不是一樣的一個模型數據了.比原來有了更多的字段,或許在之後的時候中會有或多或少的問題,好比字段重複殊不知道,好比模型傳遞,傳遞屢次以後,已經不知道哪部分是後來本身添加的屬性了...

那麼咱們使用新的數組來替代呢?咱們使用一個新的數組來替代原來的模型數組 使用一個自建的模型類來代替原來的模型類,裏面只有須要的內容,好比行高,好比全部須要顯示的內容,這樣的一個模型類來組成新的模型數組. 而後UITableView只須要處理新的模型數組來呈現便可. 可是這樣的的數組你會發現,丟失了原來的模型.好比咱們點擊某一個cell的時候咱們須要將模型傳遞到下一個界面,此時拿到的數組就不是該模型了,而是咱們本身定義的一個新的模型,這樣會不會就不太方便呢?

那麼咱們進一步擴展,咱們建立一個DetailModelManager的類,這個類裏面有這樣的一個屬性,model, 咱們在其set方法中作幾件事,第一件,就是普通的set,第二件是計算好一個cellHeight並保存爲DetailModelManager的屬性,而不是model的一個屬性,計算好(或者賦值)全部須要顯示的內容的字段,並保存爲一個個的屬性 如今的邏輯變了

這裏寫圖片描述

其實熟悉MVVM框架的同窗此時就確定笑話我了,這個不就是MVVM的VM嘛 是的,這其實就是MVVM的VM部分的一塊優點了,如今的列表數組已經變成了DetailModelManager(ViewModel)數組了,全部的須要複雜計算的部分咱們均可以以相似的方式來處理,好比我項目的一個列表

這裏寫圖片描述
此時的列表不是單純的模型數組 tableView中寫法稍變
這裏寫圖片描述
原來全部的model部分轉化成viewModel部分 那麼viewModel部分作了什麼?
這裏寫圖片描述
是的在以前的setModel的方法中我就作了不少事情,把後續須要計算的不少東西都計算結束並保存爲屬性了
這裏寫圖片描述
至於cell就簡單了,原來的model換成viewModel,原來的model的屬性,如今變成了viewModel的屬性了,並且徹底不須要任何計算.直接都是拿過來使用便可, 後期若是顯示的內容不符合需求,只須要修改viewModel的那個字段的計算便可.其餘任何地方都不須要修改.

呃,不知不覺說了部分的MVVM的內容,雖然和UITableView的性能優化沒有關係,可是畢竟是性能優化的一個實現方式,因此就一併說了一點

  • 總之就是行高必定要緩存

另外,咱們肯能會遇到這樣的需求,一個稍微複雜的cell中可能有九宮格展現圖片(如新浪微博),可能有滾動視圖展現圖片(如不少視頻類app),這個時候,咱們會發現,用來展現圖片的cell展現的內容是動態的,即數量是不定的,因而咱們不少人就都會想到,九宮格啥的或者滾動視圖啥的,咱們均可以用coolectionView來實現嘛,徹底動態保證,多好... 可是其實這樣作仍然是有不少麻煩的. 首先是層級關係,一個cell中包含collectionView等雖然不會有不少影響,可是這個cell的代碼就會變的很龐大,很麻煩.並且動態添加的方式會很消耗性能,緣由上面也差很少說了,咱們上面一直就是在作表格的性能優化,你在cell中又加一個表格,雖然不復雜,可是該遇到的性能問題一個都不會丟的所有觸發,並且或許是double. 如何作一點優化呢?首先咱們會發現諸如新浪微博的九宮格圖片展現樣式其實也是有規律的,其最多展現9張圖片.因此咱們其實沒有必要使用動態建立的方式,咱們在建立cell的時候就建立9個imageView便可.在使用或者複用的時候,根據圖片個數或者展現需求來保證其顯示或者隱藏便可.這樣的cell你會發如今層次上和你建立collectionview是同樣的,可是在展現上,其非動態獲取數據源展現和建立cell,其就是簡單的設置圖片的顯示和隱藏便可,而後根據圖片的格式,調整好約束就行了.一個簡單的處理咱們會發現效果是很明顯的,這樣能儘量的減小cell建立或從緩存池取時由於佈局子控件所消耗的時間. 因此說

  • 全部的子視圖都要預先建立
    複製代碼
  • 若是不須要顯示能夠設置hidden
    複製代碼

另外咱們是否會發現咱們在開發app的時候,A作push動做到B.若是B沒有設置背景色的話,會出現一個明顯的卡頓?因此避免顏色致使問題,咱們最好都給子視圖設置好背景色.雖然不會有太大的影響,可是仍是有點效果的 此外,柵格化也是咱們能夠關注的一點,首先柵格化是設計中的術語,可是用在咱們的開發一樣適用,當咱們遇到表格中的cell層級不少的時候,咱們是能夠作個柵格化的操做,就是將 cell 中的全部內容,生成一張獨立的圖像,在屏幕滾動時,只顯示圖像 設置屬性 self.layer.shouldRasterize = YES;雖然代碼很簡單,可是你會發現滾動的效果會很明顯,固然柵格化的同時必須指定分辨率,不然默認使用 1倍的scale 生成圖像! 須要設置 self.layer.rasterizationScale = [UIScreen mainScreen].scale; 固然既然有柵格化優化滾動,那麼還有操做來優化其餘的,好比繪製. 咱們在開發中都會用到SDWebImage等相似的圖片異步加載的.來保證每一個cell中的網絡圖片在加載數據的時候不會佔用主線程,從而來保證cell的流暢性,固然,其實咱們不只僅能作到這點,咱們還能夠設置cell的異步繪製,若是 cell 比較複雜,能夠設置cell圖層的屬性 self.layer.drawsAsynchronously = YES;代碼一樣簡單的能夠忽略,可是效果也是顯著的. 因此

  • 異步加載是很須要的

因此總的來講咱們在開發表格的時候,咱們須要關注的其實無非也就是簡單的幾點

  1. 對行高的緩存------不要頻繁的動態計算行高,少使用系統自動預估和動態行高,主動負擔cell的佈局,而不是依靠系統自動'撐開',在這塊處理上咱們可使用內存換計算的方式,把內存在模型構建完成的時候就算出來,提早保存好.以後直接使用便可.好比把行高計算放入model、viewmodel的初始化中或者設置數據的方法中,優點是不須要重複計算,減小計算行高時間花銷
  2. 子視圖提早建立,不要動態建立------對於視圖的建立,每一個人都有本身的方式,可是對於cell中來講,由於cell自己就是一個動態的,因此在對其複用或者建立的時候咱們就要儘可能避免使用動態的方式建立子視圖,不管是先removeSubViews再addSubView 亦或者是使用collectionView等動態視圖,這些都要在複用的時候避免,若是能夠,提早建立好須要的子視圖.在須要顯示的時候顯示,在不須要顯示的時候隱藏,這樣其建立的耗時工做僅在第一次建立的時候,以後的複用都不會再次發生建立和刪除視圖等操做,一個簡單的隱藏就能夠達到咱們的需求,何樂不爲?
  3. 圖片等展現異步加載------對於這一點,你們都頗有心得.或者說你們都會在開發的時候很直接的作到.若是講cell中的網絡圖片的加載使用主線程,那麼何止是cell會卡頓,簡直都是過小白的操做了.
  4. 儘可能減小cell的層級------子視圖的層級不易過多,(不只僅是cell的層級,整個應用的視圖中過多)層級若是太多,系統對視圖的渲染壓力就會相應的變大,畢竟很明顯的道理是越少越好蠻.至於如何減小cell的層級,不少方式,好比咱們使用drewRect的方式來繪製文字或者圖片,而不使用label等,好比咱們使用UItextField來充當label,這樣既能夠在某些時候僅展現文字,某些時候用來編輯文字,作到一個控件兩種使用方式(在相似須要填寫或者選擇等信息的界面中我都會這麼用),固然若是你對性能要求很高,且技術足夠,你能夠徹底手工繪製cell,對於此你能夠簡單的學習setNeedsDisplay
  5. 圖層顏色的選擇------透明圖層對渲染性能會有必定的影響,系統必須將透明圖層與下面的視圖混合起來計算顏色,並繪製出來。減小透明圖層並使用不透明的圖層來替代它們,能夠極大地提升渲染速度。
  6. 爲代理方法瘦身-----自從咱們學會的block的時候咱們會發現咱們在任何地方都習慣使用block的方式來代替代理,在cell的建立中也不例外.好比cell中包含按鈕的點擊事件,咱們如今都會使用block的方式來傳遞出來,可是不知不覺中cell的代理方法咱們會發現很龐大,其實這樣作是否真的好呢?其實不是,首先龐大的代碼就會影響閱讀,其次,每個block都會開闢一個空間,因此說你別看只寫了一個block,可是相對應的你卻付出了block*個數的內存,但其實他們的內容徹底一致,代理之因此存在是有其意義的,一味的使用block來替換代理實際上是不對的,咱們要在合適的時候用合適的方法.cell中的按鈕點擊,諸如此類的,仍然建議仍是使用代理傳遞出來.你不會發現性能上的優化,你卻能發現代碼上的優化.
  7. 減小離屏渲染------離屏渲染,指的是GPU在當前屏幕緩衝區之外新開闢一個緩衝區進行渲染操做。設置瞭如下屬性時,都會觸發離屏繪製:shouldRasterize(光柵化)masks(遮罩)shadows(陰影)edge antialiasing(抗鋸齒)group opacity(不透明)複雜形狀設置圓角等 漸變... ,咱們的cell開發雖然不在,可是不會太過度,咱們用到的比較多的就是幾種,如圓角圖片,其會形成很明顯的離屏渲染,從而形成性能上的變差.因此確定是要對其優化的,固然網絡上有不少教你設置圓角圖片的貼文,不仿看看
  8. cell柵格化------當shouldRasterize設成true時,layer被渲染成一個bitmap,並緩存起來,等下次使用時不會再從新去渲染了。實現圓角自己就是在作顏色混合(blending),若是每次頁面出來時都blending,消耗太大,這時shouldRasterize = yes,下次就只是簡單的從渲染引擎的cache裏讀取那張bitmap,節約系統資源。若是在滾動tableView時,每次都執行圓角設置,確定會阻塞UI,設置這個將會使滑動更加流暢。
  9. 異步加載------不只僅是網絡圖片的異步加載.drawsAsynchronously屬性也是咱們須要關注的.
  10. 性能優化不是惟一的固定的,若是你的性能足夠,那麼你不優化也行,若是你的性能不夠,如何優化,優化哪些都是可選的.非固定的

性能上的優化不只僅如此,不只僅是表格, 在這裏推薦一部iOS開發必看書籍,若是融會貫通,你的開發經驗足以讓別人仰視對待, ios核心動畫高級技巧(https://www.gitbook.com/book/zsisme/ios-/details)

此外歡迎訪問個人系列博客.最近相對比較忙,不多能抽出時間繼續,可是不會斷的.

系列:iOS開發-前言+大綱 blog.csdn.net/spicyShrimp…

相關文章
相關標籤/搜索