iOS核心動畫高階技術(十二) 效能調優

weixin_34239169發表於2017-12-13

Code should run as fast as necessary, but no faster. 程式碼應該執行的儘量快,而不是更快 - 理查德

在第一和第二部分,我們瞭解了Core Animation提供的關於繪製和動畫的一些特 性。Core Animation功能和效能都非常強大,但如果你對背後的原理不清楚的話也 會降低效率。讓它達到最優的狀態是一門藝術。在這章中,我們將探究一些動畫運 行慢的原因,以及如何去修復這些問題。

#CPU VS GPU 關於繪圖和動畫有兩種處理的方式:CPU(中央處理器)GPU(圖形處理器)。在現代iOS裝置中,都有可以執行不同軟體的可程式設計晶片,但是由於歷史原因,我們可以說CPU所做的工作都在軟體層面,而GPU在硬體層面。

總的來說,我們可以用軟體(使用CPU)做任何事情,但是對於影象處理,通常 用硬體會更快,因為GPU使用影象對高度並行浮點運算做了優化。由於某些原因, 我們想盡可能把螢幕渲染的工作交給硬體去處理。問題在於GPU並沒有無限制處理 效能,而且一旦資源用完的話,效能就會開始下降了(即使CPU並沒有完全佔用)

大多數動畫效能優化都是關於智慧利用GPU和CPU,使得它們都不會超出負荷。 於是我們首先需要知道Core Animation是如何在這兩個處理器之間分配工作的。 #動畫的舞臺 Core Animation處在iOS的核心地位:應用內和應用間都會用到它。一個簡單的動畫可能同步顯示多個app的內容,例如當在iPad上多個程式之間使用手勢切換, 會使得多個程式同時顯示在螢幕上。在一個特定的應用中用程式碼實現它是沒有意義 的,因為在iOS中不可能實現這種效果(App都是被沙箱管理,不能訪問別的檢視)。

動畫和螢幕上組合的圖層實際上被一個單獨的程式管理,而不是你的應用程式。 這個程式就是所謂的渲染服務。在iOS5和之前的版本是SpringBoard程式(同時管理著iOS的主屏)。在iOS6之後的版本中叫做 BackBoard 。

當執行一段動畫時候,這個過程會被四個分離的階段被打破:

  • 佈局 - 這是準備你的檢視/圖層的層級關係,以及設定圖層屬性(位置,背景 色,邊框等等)的階段。
  • 顯示 - 這是圖層的寄宿圖片被繪製的階段。繪製有可能涉及你的 - drawRect: 和 -drawLayer:inContext: 方法的呼叫路徑。
  • 準備 - 這是Core Animation準備傳送動畫資料到渲染服務的階段。這同時也是 Core Animation將要執行一些別的事務例如解碼動畫過程中將要顯示的圖片的 時間點。
  • 提交 - 這是最後的階段,Core Animation打包所有圖層和動畫屬性,然後通過 IPC(內部處理通訊)傳送到渲染服務進行顯示。

但是這些僅僅階段僅僅發生在你的應用程式之內,在動畫在螢幕上顯示之前仍然有更多的工作。一旦打包的圖層和動畫到達渲染服務程式,他們會被反序列化來形 成另一個叫做渲染樹的圖層樹(在第一章“圖層樹”中提到過)。使用這個樹狀結構,渲染服務對動畫的每一幀做出如下工作:

  • 對所有的圖層屬性計算中間值,設定OpenGL幾何形狀(紋理化的三角形)來 執行渲染
  • 在螢幕上渲染可見的三角形

所以一共有六個階段;最後兩個階段在動畫過程中不停地重複。前五個階段都在軟體層面處理(通過CPU),只有最後一個被GPU執行。而且,你真正只能控制前兩個階段:佈局和顯示。Core Animation框架在內部處理剩下的事務,你也控制不了它。

這並不是個問題,因為在佈局和顯示階段,你可以決定哪些由CPU執行,哪些交 給GPU去做。那麼改如何判斷呢? #GPU相關的操作 GPU為一個具體的任務做了優化:它用來採集圖片和形狀(三角形),執行變換,應用紋理和混合然後把它們輸送到螢幕上。現代iOS裝置上可程式設計的GPU在這 些操作的執行上又很大的靈活性,但是Core Animation並沒有暴露出直接的介面。 除非你想繞開Core Animation並編寫你自己的OpenGL著色器,從根本上解決硬體加速的問題,那麼剩下的所有都還是需要在CPU的軟體層面上完成。

寬泛的說,大多數 CALayer 的屬性都是用GPU來繪製。比如如果你設定圖層背景或者邊框的顏色,那麼這些可以通過著色的三角板實時繪製出來。如果對一個 contents 屬性設定一張圖片,然後裁剪它 - 它就會被紋理的三角形繪製出來, 而不需要軟體層面做任何繪製。

但是有一些事情會降低(基於GPU)圖層繪製,比如:

  • 太多的幾何結構 - 這發生在需要太多的三角板來做變換,以應對處理器的柵格 化的時候。現代iOS裝置的圖形晶片可以處理幾百萬個三角板,所以在Core Animation中幾何結構並不是GPU的瓶頸所在。但由於圖層在顯示之前通過IPC傳送到渲染伺服器的時候(圖層實際上是由很多小物體組成的特別重量級的對 象),太多的圖層就會引起CPU的瓶頸。這就限制了一次展示的圖層個數(見 本章後續“CPU相關操作”)。
  • 重繪 - 主要由重疊的半透明圖層引起。GPU的填充比率(用顏色填充畫素的比 率)是有限的,所以需要避免重繪(每一幀用相同的畫素填充多次)的發生。 在現代iOS裝置上,GPU都會應對重繪;即使是iPhone 3GS都可以處理高達 2.5的重繪比率,並任然保持60幀率的渲染(這意味著你可以繪製一個半的整屏的冗餘資訊,而不影響效能),並且新裝置可以處理更多。
  • 離屏繪製 - 這發生在當不能直接在螢幕上繪製,並且必須繪製到離屏圖片的上下文中的時候。離屏繪製發生在基於CPU或者是GPU的渲染,或者是為離屏圖 片分配額外記憶體,以及切換繪製上下文,這些都會降低GPU效能。對於特定圖層效果的使用,比如圓角,圖層遮罩,陰影或者是圖層光柵化都會強制Core Animation提前渲染圖層的離屏繪製。但這不意味著你需要避免使用這些效果,只是要明白這會帶來效能的負面影響。
  • 過大的圖片 - 如果檢視繪製超出GPU支援的2048x2048或者4096x4096尺寸的紋理,就必須要用CPU在圖層每次顯示之前對圖片預處理,同樣也會降低效能。

#CPU相關的操作

大多數工作在Core Animation的CPU都發生在動畫開始之前。這意味著它不會影響到幀率,所以很好,但是他會延遲動畫開始的時間,讓你的介面看起來會比較遲鈍。

以下CPU的操作都會延遲動畫的開始時間:

  • 佈局計算 - 如果你的檢視層級過於複雜,當檢視呈現或者修改的時候,計算圖層幀率就會消耗一部分時間。特別是使用iOS6的自動佈局機制尤為明顯,它應 該是比老版的自動調整邏輯加強了CPU的工作。
  • 檢視懶載入 - iOS只會當檢視控制器的檢視顯示到螢幕上時才會載入它。這對記憶體使用和程式啟動時間很有好處,但是當呈現到螢幕上之前,按下按鈕導致的許多工作都會不能被及時響應。比如控制器從資料庫中獲取資料,或者檢視從一個nib檔案中載入,或者涉及IO的圖片顯示(見後續“IO相關操作”),都會比CPU正常操作慢得多。
  • Core Graphics繪製 - 如果對檢視實現了-drawRect方法,或者 CALayerDelegate-drawLayer:inContext:方法,那麼在繪製任何東 西之前都會產生一個巨大的效能開銷。為了支援對圖層內容的任意繪製,Core Animation必須建立一個記憶體中等大小的寄宿圖片。然後一旦繪製結束之後, 必須把圖片資料通過IPC傳到渲染伺服器。在此基礎上,Core Graphics繪製就會變得十分緩慢,所以在一個對效能十分挑剔的場景下這樣做十分不好。
  • 解壓圖片 - PNG或者JPEG壓縮之後的圖片檔案會比同質量的點陣圖小得多。但是在圖片繪製到螢幕上之前,必須把它擴充套件成完整的未解壓的尺寸(通常等同 於圖片寬 x 長 x 4個位元組)。為了節省記憶體,iOS通常直到真正繪製的時候才去解碼圖片(14章“圖片IO”會更詳細討論)。根據你載入圖片的方式,第一次對圖層內容賦值的時候(直接或者間接使用 UIImageView )或者把它繪製到 Core Graphics中,都需要對它解壓,這樣的話,對於一個較大的圖片,都會佔用一定的時間。

當圖層被成功打包,傳送到渲染伺服器之後,CPU仍然要做如下工作:為了顯示螢幕上的圖層,Core Animation必須對渲染樹種的每個可見圖層通過OpenGL迴圈轉換成紋理三角板。由於GPU並不知曉Core Animation圖層的任何結構,所以必須要由CPU做這些事情。這裡CPU涉及的工作和圖層個數成正比,所以如果在你的層級關係中有太多的圖層,就會導致CPU沒一幀的渲染,即使這些事情不是你的應用程式可控的。 #IO相關操作 還有一項沒涉及的就是IO相關工作。上下文中的IO(輸入/輸出)指的是例如快閃記憶體或者網路介面的硬體訪問。一些動畫可能需要從快閃記憶體(甚至是遠端URL)來載入。一個典型的例子就是兩個檢視控制器之間的過渡效果,這就需要從一個nib檔案或者是它的內容中懶載入,或者一個旋轉的圖片,可能在記憶體中尺寸太大,需要動態滾動來載入。 IO比記憶體訪問更慢,所以如果動畫涉及到IO,就是一個大問題。總的來說,這就需要使用聰敏但尷尬的技術,也就是多執行緒,快取和投機載入(提前載入當前不需要的資源,但是之後可能需要用到)。這些技術將會在第14章中討論。 #測量,而不是猜測 於是現在你知道有哪些點可能會影響動畫效能,那該如何修復呢?好吧,其實不需要。有很多種詭計來優化動畫,但如果盲目使用的話,可能會造成更多效能上的問題,而不是修復。

如何正確的測量而不是猜測這點很重要。根據效能相關的知識寫出程式碼不同於倉促的優化。前者很好,後者實際上就是在浪費時間。

那該如何測量呢?第一步就是確保在真實環境下測試你的程式。 #真機測試,而不是模擬器 當你開始做一些效能方面的工作時,一定要在真機上測試,而不是模擬器。模擬器雖然是加快開發效率的一把利器,但它不能提供準確的真機效能引數。

模擬器執行在你的Mac上,然而Mac上的CPU往往比iOS裝置要快。相反,Mac 上的GPU和iOS裝置的完全不一樣,模擬器不得已要在軟體層面(CPU)模擬裝置 的GPU,這意味著GPU相關的操作在模擬器上執行的慢,尤其是使用 CAEAGLLayer 來寫一些OpenGL的程式碼時候。

這就是說在模擬器上的測試出的效能會高度失真。如果動畫在模擬器上執行流暢,可能在真機上十分糟糕。如果在模擬器上執行的很卡,也可能在真機上很平滑。你無法確定。

另一件重要的事情就是效能測試一定要用釋出配置,而不是除錯模式。因為當用釋出環境打包的時候,編譯器會引入一系列提高效能的優化,例如去掉除錯符號或者移除並重新組織程式碼。你也可以自己做到這些,例如在釋出環境禁用NSLog語句。你只關心釋出效能,那才是你需要測試的點。

最後,最好在你支援的裝置中效能最差的裝置上測試:如果基於iOS6開發,這意 味著最好在iPhone 3GS或者iPad2上測試。如果可能的話,測試不同的裝置和iOS 版本,因為蘋果在不同的iOS版本和裝置中做了一些改變,這也可能影響到一些性 能。例如iPad3明顯要在動畫渲染上比iPad2慢很多,因為渲染4倍多的畫素點(為 了支援視網膜顯示)。

#保持一致的幀率 為了做到動畫的平滑,你需要以60FPS(幀每秒)的速度執行,以同步螢幕重新整理 速率。通過基於 NSTimer 或者 CADisplayLink 的動畫你可以降低到30FPS,而且效果還不錯,但是沒辦法通過Core Animation做到這點。如果不保持60FPS的速率,就可能隨機丟幀,影響到體驗。

你可以在使用的過程中明顯感到有沒有丟幀,但沒辦法通過肉眼來得到具體的資料,也沒法知道你的做法有沒有真的提高效能。你需要的是一系列精確的資料。

你可以在程式中用 CADisplayLink 來測量幀率(就像11章“基於定時器的動 畫”中那樣),然後在螢幕上顯示出來,但應用內的FPS顯示並不能夠完全真實測量 出Core Animation效能,因為它僅僅測出應用內的幀率。我們知道很多動畫都在應 用之外發生(在渲染伺服器程式中處理),但同時應用內FPS計數的確可以對某些 效能問題提供參考,一旦找出一個問題的地方,你就需要得到更多精確詳細的資料 來定位到問題所在。蘋果提供了一個強大的Instruments工具集來幫我們做到這些。 #Instruments Instruments是Xcode套件中沒有被充分利用的一個工具。很多iOS開發者從沒用 過Instruments,或者只是用Leaks工具檢測迴圈引用。實際上有很多Instruments工 具,包括為動畫效能調優的東西。

你可以通過在選單中選擇Profile選項來開啟Instruments(在這之前,記住要把目標設定成iOS裝置,而不是模擬器)。然後將會顯示出圖12.1(如果沒有看到所有選項,你可能設定成了模擬器選項)。

就像之前提到的那樣,你應該始終將程式設定成釋出選項。幸運的是,配置檔案預設就是釋出選項,所以你不需要在分析的時候調整編譯策略。 我們將討論如下幾個工具:

  • 時間分析器 - 用來測量被方法/函式打斷的CPU使用情況。
  • Core Animation - 用來除錯各種Core Animation效能問題。
  • OpenGL ES驅動 - 用來除錯GPU效能問題。這個工具在編寫Open GL程式碼的時候很有用,但有時也用來處理Core Animation的工作。

Instruments的一個很棒的功能在於它可以建立我們自定義的工具集。除了你初始選擇的工具之外,如果在Instruments中開啟Library視窗,你可以拖拽別的工具到左側邊欄。我們將建立以上我們提到的三個工具,然後就可以並行使用了(見圖 12.2)。

#時間分析器 時間分析器工具用來檢測CPU的使用情況。它可以告訴我們程式中的哪個方法正 在消耗大量的CPU時間。使用大量的CPU並不一定是個問題 - 你可能期望動畫路徑對CPU非常依賴,因為動畫往往是iOS裝置中最苛刻的任務。

但是如果你有效能問題,檢視CPU時間對於判斷效能是不是和CPU相關,以及定 位到函式都很有幫助(見圖12.3)。

時間分析器有一些選項來幫助我們定位到我們關心的的方法。可以使用左側的核取方塊來開啟。其中最有用的是如下幾點:

  • 通過執行緒分離 - 這可以通過執行的執行緒進行分組。如果程式碼被多執行緒分離的 話,那麼就可以判斷到底是哪個執行緒造成了問題。
  • 隱藏系統庫 - 可以隱藏所有蘋果的框架程式碼,來幫助我們尋找哪一段程式碼造成 了效能瓶頸。由於我們不能優化框架方法,所以這對定位到我們能實際修復的 程式碼很有用。
  • 只顯示Obj-C程式碼 - 隱藏除了Objective-C之外的所有程式碼。大多數內部的Core Animation程式碼都是用C或者C++函式,所以這對我們集中精力到我們程式碼中顯 式呼叫的方法就很有用。

#Core Animation Core Animation工具用來監測Core Animation效能。它給我們提供了週期性的 FPS,並且考慮到了發生在程式之外的動畫(見圖12.4)。

Core Animation工具也提供了一系列核取方塊選項來幫助除錯渲染瓶頸:

  • Color Blended Layers - 這個選項基於渲染程度對螢幕中的混合區域進行綠到 紅的高亮(也就是多個半透明圖層的疊加)。由於重繪的原因,混合對GPU性 能會有影響,同時也是滑動或者動畫幀率下降的罪魁禍首之一。
  • ColorHitsGreenandMissesRed - 當使用 shouldRasterizep 屬性的時候, 耗時的圖層繪製會被快取,然後當做一個簡單的扁平圖片呈現。當快取再生的 時候這個選項就用紅色對柵格化圖層進行了高亮。如果快取頻繁再生的話,就 意味著柵格化可能會有負面的效能影響了(更多關於使 用 shouldRasterize 的細節見第15章“圖層效能”)。
  • Color Copied Images - 有時候寄宿圖片的生成意味著Core Animation被強制 生成一些圖片,然後傳送到渲染伺服器,而不是簡單的指向原始指標。這個選 項把這些圖片渲染成藍色。複製圖片對記憶體和CPU使用來說都是一項非常昂貴 的操作,所以應該儘可能的避免。
  • Color Immediately - 通常Core Animation Instruments以每毫秒10次的頻率更 新圖層除錯顏色。對某些效果來說,這顯然太慢了。這個選項就可以用來設定 每幀都更新(可能會影響到渲染效能,而且會導致幀率測量不準,所以不要一 直都設定它)。
  • Color Misaligned Images - 這裡會高亮那些被縮放或者拉伸以及沒有正確對 齊到畫素邊界的圖片(也就是非整型座標)。這些中的大多數通常都會導致圖 片的不正常縮放,如果把一張大圖當縮圖顯示,或者不正確地模糊影象,那 麼這個選項將會幫你識別出問題所在。
  • Color Offscreen-Rendered Yellow - 這裡會把那些需要離屏渲染的圖層高亮 成黃色。這些圖層很可能需要用 shadowPath 或者 shouldRasterize 來優 化。
  • Color OpenGL Fast Path Blue - 這個選項會對任何直接使用OpenGL繪製的 圖層進行高亮。如果僅僅使用UIKit或者Core Animation的API,那麼不會有任 何效果。如果使用 GLKView 或者 CAEAGLLayer ,那如果不顯示藍色塊的話 就意味著你正在強制CPU渲染額外的紋理,而不是繪製到螢幕。
  • Flash Updated Regions - 這個選項會對重繪的內容高亮成黃色(也就是任何 在軟體層面使用Core Graphics繪製的圖層)。這種繪圖的速度很慢。如果頻 繁發生這種情況的話,這意味著有一個隱藏的bug或者說通過增加快取或者使 用替代方案會有提升效能的空間。

這些高亮圖層的選項同樣在iOS模擬器的除錯選單也可用(圖12.5)。我們之前說過用模擬器測試效能並不好,但如果你能通過這些高亮選項識別出效能問題出在什麼地方的話,那麼使用iOS模擬器來驗證問題是否解決也是比真機測試更有效的。

#OpenGL ES驅動 OpenGL ES驅動工具可以幫你測量GPU的利用率,同樣也是一個很好的來判斷和GPU相關動畫效能的指示器。它同樣也提供了類似Core Animation那樣顯示FPS 的工具(圖12.6)。

側欄的郵編是一系列有用的工具。其中和Core Animation效能最相關的是如下幾點:

  • Renderer Utilization - 如果這個值超過了~50%,就意味著你的動畫可能對幀 率有所限制,很可能因為離屏渲染或者是重繪導致的過度混合。
  • Tiler Utilization - 如果這個值超過了~50%,就意味著你的動畫可能限制於幾 何結構方面,也就是在螢幕上有太多的圖層佔用了。

#一個可用的案例

現在我們已經對Instruments中動畫效能工具非常熟悉了,那麼可以用它在現實中解決一些實際問題。

我們建立一個簡單的顯示模擬聯絡人姓名和頭像列表的應用。注意即使把頭像圖 片存在應用本地,為了使應用看起來更真實,我們分別實時載入圖片,而不是用 – imageNamed: 預載入。同樣新增一些圖層陰影來使得列表顯示得更真實。清單12.1 展示了最初版本的實現。

#import "ViewController.h" 
#import <QuartzCore/QuartzCore.h>
@interface ViewController () <UITableViewDataSource> 
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, weak) IBOutlet UITableView *tableView; 
@end
@implementation ViewController
- (NSString *)randomName {
 NSArray *first = @[@"Alice", @"Bob", @"Bill", @"Charles", @"Dan", @"Dave", @"Ethan", @"Frank"];
 NSArray *last = @[@"Appleseed", @"Bandicoot", @"Caravan", @"Dabble", @"Ernest", @"Fortune"];
 NSUInteger index1 = (rand()/(double)INT_MAX) * [first count];
 NSUInteger index2 = (rand()/(double)INT_MAX) * [last count];
 return [NSString stringWithFormat:@"%@ %@", first[index1], last[index2]];
}
- (NSString *)randomAvatar {
 NSArray *images = @[@"Snowman", @"Igloo", @"Cone", @"Spaceship", @"Anchor", @"Key"];
 NSUInteger index = (rand()/(double)INT_MAX) * [images count];
 return images[index]; 
}
- (void)viewDidLoad {
 [super viewDidLoad];
 //set up data
 NSMutableArray *array = [NSMutableArray array]; 
 for (int i = 0; i < 1000; i++)
 {
    //add name
   [array addObject:@{@"name": [self randomName], @"image": [self randomAvatar]}];
 }
 self.items = array;
 //register cell class
 [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
 return [self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
{
 cellForRowAtIndexPath:(NSIndexPath *)indexPath
 //dequeue cell
 UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
 //load image
 NSDictionary *item = self.items[indexPath.row];
 NSString *filePath = [[NSBundle mainBundle] pathForResource:item[@"image"] ofType:@"png"]; 
 cell.imageView.image = [UIImage imageWithContentsOfFile:filePath];
 cell.textLabel.text = item[@"name"];
 //set image shadow
 cell.imageView.layer.shadowOffset = CGSizeMake(0, 5); 
 cell.imageView.layer.shadowOpacity = 0.75; cell.clipsToBounds = YES;
 //set text shadow
 cell.textLabel.backgroundColor = [UIColor clearColor]; 
 cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2); 
 cell.textLabel.layer.shadowOpacity = 0.5;
 //set image and text
 return cell; 
}
@end
複製程式碼

當快速滑動的時候就會非常卡(見圖12.7的FPS計數器)。

僅憑直覺,我們猜測效能瓶頸應該在圖片載入。我們實時從快閃記憶體載入圖片,而且沒有快取,所以很可能是這個原因。我們可以用一些很讚的程式碼修復,然後使用 GCD非同步載入圖片,然後快取。。。等一下,在開始編碼之前,測試一下假設是否成立。首先用我們的三個Instruments工具分析一下程式來定位問題。我們推測問題 可能和圖片載入相關,所以用Time Profiler工具來試試(圖12.8)。

-tableView:cellForRowAtIndexPath:中的CPU時間總利用率只有~28% (也就是載入頭像圖片的地方),非常低。於是建議是CPU/IO並不是真正的限制 因素。然後看看是不是GPU的問題:在OpenGL ES Driver工具中檢測GPU利用率 (圖12.9)。

渲染服務利用率的值達到51%和63%。看起來GPU需要做很多工作來渲染聯絡人列表。

為什麼GPU利用率這麼高呢?我們來用Core Animation除錯工具選項來檢查屏 幕。首先開啟Color Blended Layers(圖12.10)。

螢幕中所有紅色的部分都意味著字元標籤檢視的高階別混合,這很正常,因為我們把背景設定成了透明色來顯示陰影效果。這就解釋了為什麼渲染利用率這麼高了。

那麼離屏繪製呢?開啟Core Animation工具的Color Offscreen - Rendered Yellow 選項(圖12.11)。

所有的表格單元內容都在離屏繪製。這一定是因為我們給圖片和標籤檢視新增的陰影效果。在程式碼中禁用陰影,然後看下效能是否有提高(圖12.12)。

問題解決了。幹掉陰影之後,滑動很流暢。但是我們的聯絡人列表看起來沒有之前好了。那如何保持陰影效果而且不會影響效能呢?

好吧,每一行的字元和頭像在每一幀重新整理的時候並不需要變,所以看起來的圖層非常適合做快取。我們可以使用來快取圖層內容。這將會讓圖層離屏之後渲染一次然後把結果儲存起來,直到下次利用的時候去更新(見清單12.2)。

清單12.2 使用 shouldRasterize 提高效能

- (UITableViewCell *)tableView:(UITableView *)tableView
{
  ...
  cellForRowAtIndexPath:(NSIndexPath *)indexPath
  //dequeue cell
  UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
  //set text shadow
  cell.textLabel.backgroundColor = [UIColor clearColor];   
  cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2);   
  cell.textLabel.layer.shadowOpacity = 0.5;
  //rasterize
  cell.layer.shouldRasterize = YES; 
  cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
  return cell; 
}
複製程式碼

我們仍然離屏繪製圖層內容,但是由於顯式地禁用了柵格化,Core Animation就對繪圖快取了結果,於是對提高了效能。我們可以驗證快取是否有效,在Core Animation工具中點選Color Hits Green and Misses Red選項(圖12.13)。

圖12.13 Color Hits Green and Misses Red驗證了快取有效

結果和預期一致 - 大部分都是綠色,只有當滑動到螢幕上的時候會閃爍成紅色。 因此,現在幀率更加平滑了。

所以我們最初的設想是錯的。圖片的載入並不是真正的瓶頸所在,而且試圖把它置於一個複雜的多執行緒載入和快取的實現都將是徒勞。所以在動手修復之前驗證問題所在是個很好的習慣!

#總結 在這章中,我們學習了Core Animation是如何渲染,以及我們可能出現的瓶所在。你同樣學習瞭如何使用Instruments來檢測和修復效能問題。在下三章中,我們將對每個普通程式的效能陷阱進行詳細討論,然後學習如何修復。

相關文章