Texture 性能優化

這是 Texture 文檔系列翻譯,其中結合了自己的理解和工作中的使用體會。如果哪里有誤,希望指出。

  1. Texture 核心概念

  2. Texture 布局 Layout

  3. Texture 便捷方法

  4. Texture 性能優化

  5. Texture 容器 Node Containers

  6. Texture 基本控件 Node

  7. Texture 中 Node 的生命周期

Layer Backing

通過使用 layer 而非 view 可以顯著提高 app 性能,推薦在不需要觸摸事件的 node 中開啟 layer-backing。

UIKit中,手動將基于視圖的代碼轉換為 layer 很費力,并且在需要啟用觸摸處理或其他特定于視圖的功能時,需要手動將所有內容轉換回去。

在 Texture 中,將 node 的視圖及所有 subtree 轉換為 layer 非常簡單:

rootNode.isLayerBacked = YES;

需要轉換回 view 時,只需刪除這一行。

子樹柵格化 Subtree Rasterization

將整個視圖層級合成為一個圖層可以提高性能,但在UIKit中會影響可維護性和基于層級結構的推理。

在 node 中,開啟合成非常簡單:

[rootNode enableSubtreeRasterization];

上述代碼使從 rootNode 開始整個 node 層次結構合成為一層。

同步并發 Synchronous Concurrency

ASViewControllerASCellNode都有一個布爾類型neverShowPlaceholders屬性。通過將此屬性設置為YES,cell 或視圖控制器的 view 繪制完成前主線程將被堵塞。

開啟該屬性后,并不能充分利用 Texture 的性能。通常 node 已被預加載,在進入屏幕時即將完成,因此堵塞時間很短。即使rangeTuningParameters設置為0,Texture 性能也優于UIKit。因為即使主線程堵塞,subnode 也在并發執行。

查看NSSpain 2015 talk video了解其具體行為:

node.neverShowPlaceholders = YES;

通常,cell 繪制完成前進入了屏幕會顯示占位符,直到繪制完成。將neverShowPlaceholders設置為YES使 Texture 更像UIKit,即滑動時會像UIKit一樣掉幀,盡管 Texture 速度會快些。

TextureSynchronousConcurrency.jpg

圓角 Corner Rounding

對于圓角處理,很多開發人員都會選擇CALayercornerRadius屬性。但cornerRadius會嚴重影響性能,應在沒有其他選擇時才使用。這一部分將涵蓋:

  • 為何不應使用CALayercornerRadius
  • 其他更為高效實現圓角方式,以及何時使用。
  • 選擇圓角處理的流程圖。
  • Texture 實現圓角的方法。

CALayer 的 cornerRadius 耗費性能

使用CALayercornerRadius會觸發離屏渲染(offscreen rendering),以在每幀上執行裁剪操作。滾動時每秒需要在60幀上執行裁剪操作,即使內容沒有發生任何變化。這意味著 GPU 必須在每幀之間切換上下文,包括合成整個幀和裁剪。

這些對性能的消耗不會顯示在 Time Profiler 中,因為其影響的是 CoreAnimation Render Server,會造成掉幀。

圓角策略

當選擇圓角策略時,只需考慮以下三點:

  1. 圓角下(movement underneath the corner)是否有滑動。
  2. 是否有穿過圓角滑動(movement through the corner)。
  3. 四個圓角是否處于同一個 node 上,有沒有與其他 node 相交。

圓角下滑動是指圓角后面的任何運動。例如,當一個圓角 cell 在背景上滾動時,背景就是在圓角下滑動。

為了描述穿過圓角滑動,可以想象一個小的圓角滾動視圖,其中包含大很多的圖片。當在滾動視圖內縮放、平移圖片時,照片將在滾動視圖的各個角中穿過。

Texturecorner-rounding-movement.png

上圖中,藍色表示圓角下有滑動;橙色表示穿過圓角滑動。

圓角對象內部有移動,而無需移過角。下圖顯示了以綠色標志的內容,該內容從邊緣開始插入,其邊距等于圓半徑大小。當內容滾動時,它不會在角落滾動。

Texturecorner-rounding-scrolling.png

使用上述方法來調整設計以消除圓角移動的影響,以便不影響性能的情況下使用cornerRadius

最后需要注意是否四個角處于同一個 node 中,或 subnode 與圓角相交。

Texturecorner-rounding-overlap.png

預合成角 Precomposited Corners

預合成角指使用貝塞爾曲線繪制角的曲線,以在 CGContext / UIGraphicsContext 中剪切內容。在這種情況下,拐角將成為圖像本身的一部分,并被同步到CALayer中,有兩種類型的預合成角:

最佳方案為使用預合成的不透明角(precomposited opaque corner),這是最高效的方法,可實現零 alpha 混合(盡管這比避免觸發屏外渲染的重要性小很多),但其不夠靈活。如果圓角的對象需要移動,則后面的背景將需要為純色。使用 Texture 或圖片背景會很棘手,推薦使用預合成的 alpha 角。

第二種涉及貝塞爾曲線的角是預合成 alpha 角(precomposited alpha corner),此方法非常靈活,是最常用的方法之一。其會增加在這個內容上進行 alpha 混合的成本,并且 alpha 通道不透明會增加25%內存消耗,但這些消耗對于現代設備來說很小,與cornerRadius觸發的離屏渲染不在同一數量級。

預合成角要求角必須在同一 node,且不與 subnode 相交。不滿足任一條件,則需使用裁剪圓角。

當使用了shouldRasterizeDescendants后,Texture 會自動對cornerRadius進行預合成。在開啟柵格化前,請務必仔細考慮,具體可以查看子樹柵格化 Subtree Rasterization

Texture 的UIImage+ASConveniences.h提供了簡單、純色圓角實現方法,支持 alpha 和不透明創建平面顏色,圓角可以調整大小,非常適合于圖像 node 和ASButtonNode的背景占位符。

剪裁圓角 Clip Corner

Clip corner 通過向四個角放置四個不透明內容實現圓角。這種方法靈活、性能高。四個單獨 layer 對于 CPU 來說性能消耗很少。

Textureclip-corners.png

Clip Corner 適用于以下兩種情況:

  • 四個角在不同 node,或與 subnode 相交。
  • 圓角只在 node 頂部。

是否可以使用CALayer的cornerRadius

雖然很少、但也存在必須使用cornerRadius的情況。例如,在實現動畫時,圓角下滑動、穿過圓角的內容是動態的,但通常可以通過改變設計利用前面的解決方案。

屏幕沒有任何滑動時,使用cornerRadius對性能的影響小很多。只要屏幕有滑動,即使不涉及圓角部分,也會因使用cornerRadius對性能產生影響。例如,導航欄中有一個圓形元素,其下方有滾動視圖,即使它們不重疊,也會產生影響。滑動屏幕上任何內容,均會對性能產生影響,即使用戶沒有與其交互。此外,任何類型的屏幕刷新,都將因cornerRadius而產生額外開銷。

Rasterization 和 Layerbacking

雖然使用CALayershouldRasterize可以提高cornerRadius的性能,但這是一個未充分理解的選項,通常很危險。在無需重新柵格化時(即沒有滑動、點擊更改顏色,也不在移動的表視圖上),可以使用shouldRasterize屬性。通常,不建議使用shouldRasterize,其可能會導致更為嚴重的性能下降。對于本身性能不佳、堅持使用cornerRadius屬性的app來說,其能夠帶來一定的性能提升。但如果正從零構建一款 app,強烈建議選擇前面更好的圓角策略。

CALayershouldRasterize與 Texture 的node.shouldRasterizeDescendents無關,當開啟shouldRasterizeDescendents后,將阻止 subnode 創建 view 和 layer。

圓角策略選取

使用下面流程圖選取合適圓角策略:

Texturecorner-rounding-flowchart-v2.png

Texture 實現圓角的方式

在 Texture 中,有以下幾種實現圓角的方式。

使用 cornerRadius
CGFloat cornerRadius = 20.0;
    
_photoImageNode.cornerRoundingType = ASCornerRoundingTypeDefaultSlowCALayer;
_photoImageNode.cornerRadius = cornerRadius;
使用 precomposition
CGFloat cornerRadius = 20.0;
    
_photoImageNode.cornerRoundingType = ASCornerRoundingTypePrecomposited;
_photoImageNode.cornerRadius = cornerRadius;

使用 clipping

CGFloat cornerRadius = 20.0;

_photoImageNode.cornerRoundingType = ASCornerRoundingTypeClipping;
_photoImageNode.backgroundColor = [UIColor whiteColor];
_photoImageNode.cornerRadius = cornerRadius;
使用 willDisplayNodeContentWithRenderingContext 設置裁剪路徑
CGFloat cornerRadius = 20.0;
    
// Use the screen scale for corner radius to respect content scale
CGFloat screenScale = UIScreen.mainScreen.scale;
_photoImageNode.willDisplayNodeContentWithRenderingContext = ^(CGContextRef context, id drawParameters) {
    CGRect bounds = CGContextGetClipBoundingBox(context);
    CGFloat radius = cornerRadius * screenScale; 
    UIImage *overlay = [UIImage as_resizableRoundedImageWithCornerRadius:radius
                                                             cornerColor:[UIColor clearColor]
                                                               fillColor:[UIColor clearColor]];
    [overlay drawInRect:bounds];
    [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:radius] addClip];
};
使用 ASImageNode 獲取圓形圖像并添加邊框

非常適合于獲取圓形頭像。

CGFloat cornerRadius = 20.0;

_photoImageNode.imageModificationBlock = ASImageNodeRoundBorderModificationBlock(5.0, [UIColor orangeColor]);

上一篇:Texture 便捷方法

下一篇:Texture 容器 Node Containers

歡迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/Texture%20%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.md

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容