這是 Texture 文檔系列翻譯,其中結合了自己的理解和工作中的使用體會。如果哪里有誤,希望指出。
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
ASViewController
和ASCellNode
都有一個布爾類型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 速度會快些。
圓角 Corner Rounding
對于圓角處理,很多開發人員都會選擇CALayer
的cornerRadius
屬性。但cornerRadius
會嚴重影響性能,應在沒有其他選擇時才使用。這一部分將涵蓋:
- 為何不應使用
CALayer
的cornerRadius
。 - 其他更為高效實現圓角方式,以及何時使用。
- 選擇圓角處理的流程圖。
- Texture 實現圓角的方法。
CALayer 的 cornerRadius 耗費性能
使用CALayer
的cornerRadius
會觸發離屏渲染(offscreen rendering),以在每幀上執行裁剪操作。滾動時每秒需要在60幀上執行裁剪操作,即使內容沒有發生任何變化。這意味著 GPU 必須在每幀之間切換上下文,包括合成整個幀和裁剪。
這些對性能的消耗不會顯示在 Time Profiler 中,因為其影響的是 CoreAnimation Render Server,會造成掉幀。
圓角策略
當選擇圓角策略時,只需考慮以下三點:
- 圓角下(movement underneath the corner)是否有滑動。
- 是否有穿過圓角滑動(movement through the corner)。
- 四個圓角是否處于同一個 node 上,有沒有與其他 node 相交。
圓角下滑動是指圓角后面的任何運動。例如,當一個圓角 cell 在背景上滾動時,背景就是在圓角下滑動。
為了描述穿過圓角滑動,可以想象一個小的圓角滾動視圖,其中包含大很多的圖片。當在滾動視圖內縮放、平移圖片時,照片將在滾動視圖的各個角中穿過。
上圖中,藍色表示圓角下有滑動;橙色表示穿過圓角滑動。
圓角對象內部有移動,而無需移過角。下圖顯示了以綠色標志的內容,該內容從邊緣開始插入,其邊距等于圓半徑大小。當內容滾動時,它不會在角落滾動。
使用上述方法來調整設計以消除圓角移動的影響,以便不影響性能的情況下使用cornerRadius
。
最后需要注意是否四個角處于同一個 node 中,或 subnode 與圓角相交。
預合成角 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 來說性能消耗很少。
Clip Corner 適用于以下兩種情況:
- 四個角在不同 node,或與 subnode 相交。
- 圓角只在 node 頂部。
是否可以使用CALayer的cornerRadius
雖然很少、但也存在必須使用cornerRadius
的情況。例如,在實現動畫時,圓角下滑動、穿過圓角的內容是動態的,但通常可以通過改變設計利用前面的解決方案。
屏幕沒有任何滑動時,使用cornerRadius
對性能的影響小很多。只要屏幕有滑動,即使不涉及圓角部分,也會因使用cornerRadius
對性能產生影響。例如,導航欄中有一個圓形元素,其下方有滾動視圖,即使它們不重疊,也會產生影響。滑動屏幕上任何內容,均會對性能產生影響,即使用戶沒有與其交互。此外,任何類型的屏幕刷新,都將因cornerRadius
而產生額外開銷。
Rasterization 和 Layerbacking
雖然使用CALayer
的shouldRasterize
可以提高cornerRadius
的性能,但這是一個未充分理解的選項,通常很危險。在無需重新柵格化時(即沒有滑動、點擊更改顏色,也不在移動的表視圖上),可以使用shouldRasterize
屬性。通常,不建議使用shouldRasterize
,其可能會導致更為嚴重的性能下降。對于本身性能不佳、堅持使用cornerRadius
屬性的app來說,其能夠帶來一定的性能提升。但如果正從零構建一款 app,強烈建議選擇前面更好的圓角策略。
CALayer
的shouldRasterize
與 Texture 的node.shouldRasterizeDescendents
無關,當開啟shouldRasterizeDescendents
后,將阻止 subnode 創建 view 和 layer。
圓角策略選取
使用下面流程圖選取合適圓角策略:
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 便捷方法
歡迎更多指正: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