iOS性能調優之--tableView優化

2022-01-09 Cocoa開發者社區
背景

作者基於之前自己所寫的Swift項目--仿照推特客戶端用純Swift寫的一個項目,目前已經公開放在GitHub上(https://github.com/waitwalker/MyTwitter); 接口用Python寫的幾個(https://github.com/waitwalker/MyTwitterAPI),目前所實現的功能是登錄,註冊,發推,首頁列表等功能,其他頁面都是一些假數據.這裡不主要分析項目了,有時間在詳細說一下.本文的主要重點是說說自己對性能調優--tableView控制項優化的一點理解,有些問題理解的不是很透徹,希望大家能多給些意見,建議,謝謝.下圖是整個項目的總覽(文章中多是gif動態,尺寸較大,使用流量看得慎重):

項目總覽1.gif

項目總覽2.GIF

有一句話說:過早的優化是萬惡之源, 過早並不是開發過程的早期,而是在還沒弄清楚業務需求的情形下去做所謂的優化,有時候會適得其反——費時、費力、不討好。正確的方法是,先有質量地實現你的需求,寫夠測試用例,然後做profile去找到性能的瓶頸,考慮究竟哪些地方應該優化,應該如何優化,哪些不應該優化.


文章結構.png

作者對一些顯示的垂直信號,水平信號原理不是了解,大家如果想深入了解的可以查一下相關資料,相關大神也有總結,或許從硬體底層優化可能效果會更好.造成tableView卡頓的原因,從硬體上來說無非就兩個,一個是CPU原因,一個是GPU原因.如果CPU核數較多,並發處理問題的能力也就越強,處理大量計算也不在話下;如果GPU顯存夠大,渲染能力足夠強,處理複雜圖形界面也就得心應手.但是,硬體的配置是有限度的,我們的目標是在有限度的硬體上,讓其發揮最大限度的作用.這個也就是造成tableView卡頓的程序原因(軟體原因)--卡住了主線程,本文將主要討論是從程序角度討論怎麼優化tableView問題.

基礎

最基本的就是減少cell的自定義類型,重用cell,每次只繪製屏幕顯示cell的數量,其它cell從緩存中取.這些基礎大家應該比我了解,這裡不再陳述了.

1. 減輕CPU負荷

我們知道CPU的主要負責快速調度任務,大量計算工作,所以在tableView快速滾動的過程中讓CPU的計算量降低是優化應該考慮的方向.下面總結了三個方面來儘可能的降低CPU計算:


1.1提前計算好cell的高度,緩存在相應的數據源模型中

大家都已經知道tableView的代理回調方法中,先調用的是返回cell高度的方法,然後在返回實例化cell的方法.我們可以在返回cell高度時,提前計算好cell的高度,緩存到數據源模型中.例如:MTTHomeModel對應的是首頁cell的數據模型,我們可以看到下面兩個變量,是來存儲cell的高度和內容高度的,:

var cellHeight:CGFloat?
var contentHeight:CGFloat?

在獲取數據後臺數據的時候,把cell高度計算出來,緩存起來:

homeModel.contentHeight  = self.calculateTextHeight(text: homeModel.contentTextString!) + 150
if(homeModel.retwitterType?.count)! > Int(0)
{
   homeModel.cellHeight = 255 + homeModel.contentHeight! - 150
} else{
   homeModel.cellHeight = 230 + homeModel.contentHeight! - 150 + 15
}

在返回cell高度的方法中,直接讀取緩存的高度,而不需要在重新計算了.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
   {
       if self.homeDataArray != nil
       {
           let homeModel = homeDataArray![indexPath.row]
           return homeModel.cellHeight!
       } else
       {
           return 300
       }
   }

1.2 儘可能的降低storyboard,xib等使用度

之前看到一些大家分享的相關資料,通過Interface知道xib或者storyboard本身就是一個xml文件,添加刪除控制項必然中間多了一個encode/decode過程,增加了cpu的計算量.並且 還要避免臃腫的 XIB 文件,因為XIB文件在主線程中進行加載布局.當用到一些自定義View或者XIB文件時,XIB的加載會把所有內容加載進來,如果XIB裡面的一些控制項並不會用到,這就可能造成一些資源的消耗浪費.網上有說:Storyboard 沒這個問題,只會按需加載,這個作者還沒有去考證.


1.3 滑動過程中儘量減少重新布局

自動布局就是給控制項添加約束,約束最終還是轉換成frame.所以,在滿足業務需求情況下,如果圖層層次較為複雜,要儘量減少自動布局約束,轉為手動計算布局,大量的約束重疊也會增加cpu的計算量


作者在獲取到數據源時,每次都重新布局控制項,這個也是一個重要開銷,也是接下來需要優化的方向.

private funclayoutSubview(homeModel:MTTHomeModel) -> Void
{
      topLineView?.snp.makeConstraints({ (make) in
          make.left.right.top.equalTo(0)
          make.height.equalTo(0.3)
      })
}

2. 不要阻塞主線程

UIKit的工作基本上都是在主線程上進行,界面繪製,用戶輸入響應等等.當所有的代碼邏輯都放在主線程時,某些耗時任務可能會卡住主線程造成程序無法響應,流暢度降低等問題;在主線程中繪製大量界面圖層,網絡I/O,磁碟I/O等都可以造成界面卡頓現象.


下面我們通過Xcode自帶的調試工具Instruments來看看項目界面的流暢度,及其一些建議,Instruments給我提供了各種各樣的調試查看工具,下面簡單介紹一下:


        1)Blank: 創建一個空的模板,可以從Library庫中添加其他模板.
        2)Activity Monitor: 監控進程級別的CPU,內存,磁碟,網絡使用情況,可以得到你的應用程式在手機運行時總共佔用的內存大小.
        3)Allocations: 跟蹤過程的匿名虛擬內存和堆的對象提供類名和可選保留/釋放歷史,可以檢測每一個堆對象的分配內存情況.
        4)Cocoa Layout : 觀察NSLayoutConstraint對象的改變,幫助我們判斷什麼時間什麼地點的constraint是否合理.觀察約束變化,找出布局代碼的問題所在.
        5)Core Animation: 這個模塊顯示程序顯卡性能以及CPU使用情況,查看界面流暢度.
        6)CoreData: 這個模塊跟蹤Core Data文件系統活動.
        7)Counters : 收集使用時間或基於事件的抽樣方法的性能監控計數器(PMC)事件.
        8)Energy Log: 耗電量監控.
        9)File Activity: 檢測文件創建,移動,變化,刪除等.
        10)Leak: 一般的措施內存使用情況,檢查洩漏的內存,並提供了所有活動的分配和洩漏模塊的類對象分配統計信息以及內存地址歷史記錄.
        11)Metal System Trace: Metal API是apple 2014年在ios平臺上推出的高效底層的3D圖形API,它通過減少驅動層的API調用CPU的消耗提高渲染效率.
        12)Network: 用連結工具分析你的程序如何使用TCP/IP和UDP/IP連結.
        13)SceneKit: 3D性能狀況分析.
        14)System Trace: 系統跟蹤,通過顯示當前被調度線程提供綜合的系統表現,顯示從用戶到系統的轉換代碼通過兩個系統調用或內存操作.
        15)System Usage: 這個模板記錄關於文件讀寫,sockets,I/O系統活動,輸入輸出.
        16)Time Profiler(時間探查): 執行對系統的CPU上運行的進程低負載時間為基礎採樣.
        17)Zombies: 測量一般的內存使用,專注於檢測過度釋放的野指針對象,也提供對象分配統計,以及主動分配的內存地址歷史.


本文主要使用的是Instruments中的第5個工具:Core Animation(圖形性能),這個模塊顯示程序顯卡性能以及CPU使用情況,查看界面流暢度.


首先我們必須要把源碼安裝到測試設備上,1)連接Xcode運行程序;2)然後選擇快捷鍵(Command + Control + i)調出Instruments,選擇Core Animation.打開後我們可以看到Debug Options裡面有多個調試選項,下面我們挨個儘量來分析看一下:

2.1 Color Blended Layers

這個選項選項基於渲染程度對屏幕中的混合區域進行綠到紅的高亮顯示,越紅表示性能越差,會對幀率等指標造成較大的影響.紅色通常是由於多個半透明圖層疊加引起.


作者項目可能項目比較簡單,圖層也不是很複雜,所以通過Color Blended Layers查看,深紅色並不是很明顯,在快速滑動的過程中,幀率依然能夠保持在55+以上,並且圖層中也沒有大量的深紅色區域出現.

ColorBlendedLayers1-1.png

ColorBlendedLayers1-2-.gif

2.2 Color Hits Green and Misses Red

當UIView.layer.shouldRasterize = YES 時,耗時的圖片繪製會被緩存,並當做一個簡單的扁平圖片來呈現.這時候,如果頁面的其他區塊(比如 UITableViewCell 的復用)使用緩存直接命中,就顯示綠色,反之,如果不命中,這時就顯示紅色.紅色越多,性能越差.因為柵格化生成緩存的過程是有開銷的,如果緩存能被大量命中和有效使用,則總體上會降低開銷,反之則意味著要頻繁生成新的緩存,這會讓性能問題雪上加霜.

ColorHitsGreenandMissesRed2-1.gif

這裡筆者還要提一下圖片的加載方式,我們知道圖片的一般加載方式有兩種:imageNamed 和imageWithContentsOfFile;它們的不同在於前者會對圖片進行緩存,而後者只是簡單的從文件加載文件.如果你加載的是大圖,並且只會用到一次,比如歡迎引導圖,那麼就沒必要緩存這個圖片,可以使用[UIImage imageWithContentsOfFile:],用完就釋放了.如果會多次使用到一張圖時,用[UIImage imageNamed:] 就會高效很多,因為這種加載圖片方式有一個緩存機制.YYImage實現原理應該就是後面這種思路,自己手動添加緩存.

2.3 Color Copied Images

對於 GPU 不支持的色彩格式的圖片只能由 CPU 來處理,把這樣的圖片標為藍色.藍色越多,性能越差.因為,我們不希望在滾動視圖的時候,由 CPU 來處理圖片,這樣可能會對主線程造成阻塞.

ColorCopiedImages3-1.gif

2.4 Color Immediately

通常 Core Animation Instruments 以每毫秒 10 次的頻率更新圖層調試顏色。對某些效果來說,這顯然太慢了.這個選項就可以用來設置每幀都更新(可能會影響到渲染性能,而且會導致幀率測量不準,所以不要一直都設置它).

ColorImmediately4-1.gif

2.5 Color Misaligned Images

這個選項檢查了圖片是否被縮放,以及像素是否對齊.被放縮的圖片會被標記為黃色,像素不對齊則會標註為紫色.黃色,紫色越多,性能越差.

ColorMisalignedImages5-1.gif

這裡UI在切圖的時候儘量切得尺寸和你控制項的尺寸保持一致,儘量讓圖片保持原始尺寸.筆者這裡所用圖片幾乎全部拉伸,由於圖片都是從本地加載的,沒有經過處理.

2.6 Color Offscreen-Rendered Yellow

這個選項會把那些離屏渲染的圖層顯示為黃色.黃色越多,性能越差.這些顯示為黃色的圖層很可能需要用 shadowPath 或者 shouldRasterize 來優化.

ColorOffscreen-RenderedYellow6-1.gif

離屏渲染,即 Off-Screen Rendering.與之相對的是 On-Screen Rendering,即在當前屏幕渲染,意思是渲染操作是用於在當前屏幕顯示的緩衝區進行.那麼離屏渲染則是指圖層在被顯示之前是在當前屏幕緩衝區以外開闢的一個緩衝區進行渲染操作.


離屏渲染需要多次切>換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以後,將離屏緩衝區的渲染結果顯示到屏幕上又需要將上下文環境從離屏切換到當前屏幕,而上下文環境的切換是一項高開銷的動作.
一般對控制項屬性操作會觸發離屏渲染:


        1)陰影(UIView.layer.shadowOffset/shadowRadius/…)
        2)圓角(當 UIView.layer.cornerRadius 和UIView.layer.maskToBounds 一起使用時)
        3)圖層蒙板


在實際開發中應儘量避免觸發離屏渲染.


2.7 Color OpenGL Fast Path Blue

這個選項會把任何直接使用OpenGL 繪製的圖層顯示為藍色.藍色越多,性能越好.如果僅僅使用 UIKit 或者 Core Animation 的 API,那麼不會有任何效果.如果使用 GLKView 或者 CAEAGLLayer,那如果不顯示藍色塊的話就意味著你正在強制 CPU 渲染額外的紋理,而不是繪製到屏幕.

總結

任何優化都是以業務需求為前提,在滿足基本需求的情況下,逐步提高代碼的質量,提升程序性能,不僅是自我能力的表現,也能從中獲得一些收穫及成就感.以上優化方向思路也是在前人總結的基礎上,作者在自己的項目中的簡單應用,裡面還有許許多多需要改進提升的地方,也希望大家能給一些深層次上的建議意見.


參考資料

https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/


作者:偶爾登南山
連結:https://www.jianshu.com/p/5182234b2e1c

更多推薦:

相關焦點

  • 譯文|RedHat 性能調優
    性能優化是為了提高性能性能,減少資源消耗和應用對系統的影響。
  • 如何快速掌握Java的性能調優技巧
    如何快速掌握Java的性能調優技巧 zhisheng的blog 發表於 2020-02-22 15:44:35 大多數開發者認為性能優化是一個複雜的話題,它需要大量的工作經驗和相關知識理論
  • 通向架構師的道路(第四天)之 Tomcat 性能調優
    ,我們得知了決定性能測試的幾個重要指標,它們是:吞吐量ResponsetimeCpuloadMemoryUsage我們也在第三天的學習中對Apache做過了一定的優化,使其最優化上述4大核心指標的讀數,那麼我們的Apache調優了,我們的Tomcat也作些相應的調整,當完成今的課程後,到時你的「小貓」到時真的會「飛」起來的,所以請用心看完
  • 系統架構性能優化思路
    來源:https://4m.cn/rN8IB今天談下業務系統性能問題分析診斷和性能優化方面的內容。這篇文章重點還是談已經上線的業務系統後續出現性能問題後的問題診斷和優化重點。對於單用戶性能問題往往比較容易測試和驗證,對於並發性能問題我們可以在測試環境進行加壓測試和驗證,以判斷並發下的性能。如果是單用戶本身就存在性能問題,那麼大部分問題都出在程序代碼和SQL需要進一步優化上面。如果是並發性能問題,我們就需要進一步分析資料庫和中間件本身的狀態,看是否需要對中間件進行性能調優。
  • Spark性能優化指南——基礎篇
    因此,想要用好Spark,就必須對其進行合理的性能優化。Spark的性能調優實際上是由很多部分組成的,不是調節幾個參數就可以立竿見影提升作業性能的。我們需要根據不同的業務場景以及數據情況,對Spark作業進行綜合性的分析,然後進行多個方面的調節和優化,才能獲得最佳性能。
  • JVM性能調優
    這裡有幾個比較重要的指標:當然,和CAP原則一樣,同時滿足一個程序內存佔用小、延遲低、高吞吐量是不可能的,程序的目標不同,調優時所考慮的方向也不同,在調優之前,必須要結合實際場景,有明確的的優化目標,找到性能瓶頸,對瓶頸有針對性的優化,最後進行測試,通過各種監控工具確認調優後的結果是否符合目標。
  • DBA必讀:資料庫性能調優的五大絕招
    【IT168 技術】當資料庫的性能變得糟糕時,IT人員能扭轉局面。  生產資料庫的性能會隨著業務和數據的增長而嚴重遲鈍。每一個關鍵資料庫性能的降低都可能會導致全面的業務損失。  從技術上講,性能優化可以從許多不同的層次下手——應用程式可以優化,資料庫可以調整,或建立新的系統架構。
  • JVM性能調優實踐
    這裡有幾個比較重要的指標:當然,和CAP原則一樣,同時滿足一個程序內存佔用小、延遲低、高吞吐量是不可能的,程序的目標不同,調優時所考慮的方向也不同,在調優之前,必須要結合實際場景,有明確的的優化目標,找到性能瓶頸,對瓶頸有針對性的優化,最後進行測試,通過各種監控工具確認調優後的結果是否符合目標。
  • SQL性能調優:查找慢查詢的5種方法
    SQL性能調優是一場永無止盡的鬥爭。我不是一名DBA,而是一名15年的開發人員。我從開始用SQL Server資料庫工作,一直到用Stackify的大SQL Azure資料庫。這麼多年來,我看到了一切。在本文中,我將提供一些技巧,以便開發人員可以在SQL Server中查找SQL慢查詢並進行性能調優。
  • UIKit性能調優實戰講解
    在使用UIKit的過程中,性能優化是永恆的話題。
  • A-Tune:基於AI的自優化能力,讓應用發揮極致性能
    當前硬體和基礎軟體組成的應用環境涉及高達7000多個配置對象,隨著業務複雜度和調優對象的增加,調優所需的時間成本呈指數級增長,導致調優效率急劇下降,給用戶帶來巨大挑戰。    l AI技術與作業系統碰撞出「新火花」  A-Tune利用AI技術,通過對業務系統的底層性能數據進行分析和建模,構建精準的場景系統畫像,針對負載模型匹得出合適的資源模型,並制定對應的調度優化策略,對業務進行更細粒度的調優
  • GC算法 + 垃圾收集器 + 性能調優實戰
    Parallel Scavenge收集器提供了很多參數供用戶找到最合適的停頓時間或最大吞吐量,如果對於收集器運作不太了解的話,手工優化存在的話可以選擇把內存管理優化交給虛擬機去完成也是一個不錯的選擇。Serial Old收集器Serial收集器的老年代版本,它同樣是一個單線程收集器。
  • Spark調優101
    針對Spark各模塊的優化策略:例如對於SparkSQL而言,數據存儲方式與SQL語句的優化也是優化重點。在系統調優過程中,每一項調優策略就好比一件武器,而系統的性能就是一環環的靶標,神槍手總是要優先了解自己手中的武器,並在執行特定的任務時選用最優的武器。
  • Spark性能優化指南
    繼基礎篇講解了每個Spark開發人員都必須熟知的開發調優與資源調優之後,本文作為《Spark性能優化指南》的高級篇,將深入分析數據傾斜調優與shuffle調優,以解決更加棘手的性能問題。調優概述有的時候,我們可能會遇到大數據計算中一個最棘手的問題——數據傾斜,此時Spark作業的性能會比期望差很多。
  • 一行代碼讓你的TableView動起來-iOS動畫
    animations:^{            cell.transform = CGAffineTransformIdentity;        } completion:^(BOOL finished) {                    }];    }}主要思路為:獲得tableview
  • Linux 性能調優工具9張圖 - 你肯定會用到
    在下方公眾號後臺回覆:面試手冊,可獲取傑哥匯總的 3 份面試
  • Java性能優化指南(一)
    性能調優是科學和藝術的結合,即需要嚴格的數據收集和分析,有時候也需要給出一些直覺的判斷,特別是在一些需要進行取捨權衡的地方。分析可以定位為題,解決問題,有時候需要一些經驗和直覺的判斷。,處理最為複雜,如果業務已經遺失,不要輕易進行重構良好的編碼習慣,良好的編碼習慣能規避必然會影響性能的代碼和設計留意資料庫的性能,在多數情況下,資料庫的性能總有提升的空間,對於業務系統,大量時間會消耗在對資料庫的操作上,對這部分的優化可以達到立竿見影的效果。
  • 實用:9 個可以快速掌握的 Java 性能調優技巧
    在明確必要之前別急著優化使用分析器找到真正的瓶頸為整個應用程式創建一個性能測試套件優先關注最大瓶頸
  • Python 環境下的自動化機器學習超參數調優
    選自Medium,作者:William Koehrsen,機器之心編譯。機器學習算法的性能高度依賴於超參數的選擇,對機器學習超參數進行調優是一項繁瑣但卻至關重要的任務。本文介紹了一個使用「Hyperopt」庫對梯度提升機(GBM)進行貝葉斯超參數調優的完整示例,並著重介紹了其實現過程。
  • etcd 性能測試與調優
    快照調優為 v2 後端存儲創建快照的代價是很高的,所以只用當參數累積到一定的數量時,Etcd 才會創建快照文件。默認情況下,修改數量達到 10000 時才會建立快照。如果 Etcd 的內存使用和磁碟使用過高,那麼應該嘗試調低快照觸發的閾值,具體請參考如下命令。