原文的題目是 Designing for iOS: Graphics & Performance, ---Begin---在之前的文章裡,我們探討了基於多種不同技術來實現自訂的UIButton,當然不同的技術所涉及到的代碼複雜度和難度也不一樣。但是我也有意提到了基於不同方法的實現所體現出的效能表現也不一一相同。 【在螢幕背後的東西】為了瞭解效能是如何受到影響的,我們需要進一步地觀察iOS裡圖形實現背後的一些內容。下面這張圖呈現了不同的frameworks和libraries之間的一些聯絡:在最頂層的就是UIKit,一個在iOS中用來系統管理使用者圖形互動的Objc進階的架構,它由一系列的集合類構成,例如UIButton、UILabel,每一個都負責他們指定的UI Control角色。UIKit本身構建在一個叫CZ喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcmUgQW5pbWF0aW9utcS/8rzc1q7Jz6Osy/zS8s6qsbvTw9PatKbA7bj8zqrHv7TztcTGvbustcTXqrOh0Ke5+7b40v3I609TIFggTGVvcGFyZLrNaU9Ttviz9sP7oaMKCiAKCtTZzfnPwsnuyOvSu7Xjvs3Kx09wZW5HTCBFU8HLo6zSu7j208PU2tLGtq/J6LG4yc+75tbGMkS6zTNEvMbL47v6zbzQzrXEserXvL+q1LS/4qOsy/y547e6tdixu9PD1NrTzs+3tcTNvNDOu+bWxsnPo6zNrNH51NpDb3JlIEFuaW1hdGlvbrrNVUlLaXTW0Leiu9PXxce/tPO1xNf308OhowoKIAoK1+6689K7sr+31r7NysdDb3JlIEdyYXBoaWNzo6zU+L6t1NpRdWFydHqjqNK7uPa7+dPaQ1BVtcS75tbG0v3H5qOs1NpPUyBYz7XNs8nPs/W0zsK2wbOjqdbQsbvS/cjroaPV4sG9uPa9z86qtdey47XEv/K83La8ysfTw0PT79HUseDQtLXEoaMKCiAKCsnPzbzW0LXE1+6118/C0rvQ0MrH07K8/rLjo6zTyUdQVbrNQ1BV1+mzyaGjCgogCgrO0sPHvq2zo8u1tb21xNOyvP6808vZxuTKtcrH1rhPcGVuR0wsQ29yZSBBbmltYXRpb24vVUlLaXS7+dPaR1BV1q7Jz7bUvMbL47v6zbzQzrrPs8nS1Lywu+bWxrXEyrXP1qOs1rG1vcS/x7DOqta5o6xpT1PJz7XE07K8/rzTy9nE3MGmu7nKx7TztPPB7M/I0+thbmRyb2lko6y689Xf08nT2tLAwLVDUFW1xLvm1sajrL74tPO24Mr9tcS2r7utyrXP1ra8u+HIw8jLuNC+9cP3z9S1xL+ottmhowoKIAoKtvjA68bBu+bWxihPZmZzY3JlZW4gZHJhd2luZym1xLuwvs3Kx9a4R1BV0rux39TatbHHsMbBxLvJz7340NC75tbGo6y2+MHt0rux39TaxsHEu7u5w7vT0LSmwO3NvM/x0MXPotaux7DNqLn9Q1BVwLTJ+rPJzbzP8dDFz6K1xLSmwO25/bPMoaPU2mlPU7Wx1tCjrMDrxsG75tbG1NrS1M/CtcTH6b/2z8K74dfUtq+0pbeio7oKPHByZSBjbGFzcz0="brush:java;">* Core Graphics(任何以CG開頭的類)* 在drawRect方法裡,甚至是空方法實現* 所有shouldRasterize屬性是YES的CALayers對象 * 所有用了masks(setMasksToBounds)和動態陰影的(setShadow*)的CALayers對象* 所有文字的繪製,包括CoreText* Group opacity(UIViewGroupOpacity) 總地來說,當有涉及到動畫的時候離屏繪製就會影響到效能,你可以通過Instruments進行真機調試,從而檢測到底是哪部分UI進行中離屏繪製:1.接入裝置 2.在XCode的Developer Applications裡開啟Instruments(Command+Shift+i) 3.選擇iOS>Graphics>Core Animation template 4.開啟詳情面板,選擇適當的視窗模式 5.選擇你的target裝置 6.檢查Color Offscreen-Rendered Yellow的debug選項 7.在你裝置上所有的離屏繪製都會呈現出黃色的色調現在讓我們逐一檢查上一篇文章裡涉及的一些技術點的效能表現。 【預資源載入繪製】使用UIImage來載入原先就在磁碟中的圖片作為自訂UIButton的背景圖片的話,完全依賴與GPU的繪製。而且如果用可resizable的圖片,也可適當避免當背景大小動態變換的時候需要不同大小的圖片,這樣可以讓我們App的bundle做得更小,而且在繪製像素的圖的時候也重複地利用了硬體加速。 【使用CALayer】因為基於CALayer實現的方法裡我們使用到了遮罩去繪製圓角的效果,所以這裡會涉及到離屏繪製。當我們使用Core Animation架構的時候,如果上面提到的用遮罩繪製圓角的屬性是開啟的話,我們也必須明確地禁止使用動畫效果。作為底線,除非你非要進行一個動畫轉場效果,否則這種方法並不適用。 【使用drawRect】drawRect方法依賴Core Graphics架構來進行自訂的繪製,但這種方法主要的缺點就是它處理touch事件的方式:每次按鈕被點擊後,都會用setNeddsDisplay進行強制重繪;而且不止一次,每次單時間點事件觸發兩次執行。這樣的話從效能的角度來說,對CPU和記憶體來說都是欠佳的。特別是如果在我們的介面上有多個這樣的UIButton執行個體。 【技巧混合 hybrid approach】這樣來說的話,是不是就意味著使用預資源載入進行繪製就是唯一可行的方案了呢?答案是否定的。如果你堅持需要用代碼來靈活地進行繪製的話,還是有一些最佳化代碼還有減少效能消耗方面的技巧的。有一種可行的方案就是建立stretchable的位元影像並在多個執行個體對象之間進行重用。 我們按照在之前的教程的相同步驟建立一個新的UIButton的子類,然後如下定義一些靜態變數
// In CBHybrid.m #import "CBHybrid.h" @implementation CBHybrid // Resizable background image for normal state static UIImage *gBackgroundImage; // Resizable background image for highlighted state static UIImage *gBackgroundImageHighlighted; // Background image border radius and height static int borderRadius = 5; static int height = 37;
接下來我們把CBBezie內drawRect裡的代碼換個地方,通過一系列的改變之後:我們可以建立一個resizable的Image用來取代之前固定尺寸的Image,然後我們可以持有該靜態對象並便於重用
- (UIImage *)drawBackgroundImageHighlighted:(BOOL)highlighted { // Drawing code goes here }首先,我們需要知道我們resizable Image的寬,為了最佳化效能,我們應該在圖片的中間保留一個像素的寬
float width = 1 + (borderRadius * 2);
高的話在這個case裡就不是非常重要了,因為這個按鈕的高度對漸層層來說已經足夠可見,設定為37pt的話也是出於和其餘幾個按鈕的高度相同的原因。搞定之後,我們需要一個bitmap context來進行繪製,搞起~
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0.0);CGContextRef context = UIGraphicsGetCurrentContext();CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UIGraphicsBeginImageContextWithOptions第二個參數為NO的話確保我們建立的Image context是透明的(帶Alpha),最後的參數是scale factor(螢幕密度),如果是0的話就是當前裝置的預設scale factor。接下來的代碼就和我們之前用Core Graphics來實現CBBezier的Demo裡用到的非常相像了。我們用highlighted參數替換預設的self.highlighted屬性,把用作更新介面的映像的資訊值儲存下來。
// Gradient Declarations// NSArray *gradientColors = ... // Draw rounded rectangle bezier pathUIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, width, height) cornerRadius: borderRadius]; // Use the bezier as a clipping path[roundedRectanglePath addClip]; // Use one of the two gradients depending on the state of the buttonCGGradientRef background = highlighted? highlightedGradient : gradient; // Draw gradient within the pathCGContextDrawLinearGradient(context, background, CGPointMake(140, 0), CGPointMake(140, height-1), 0); // Draw border// [borderColor setStroke... // Draw Inner Glow// UIBezierPath *innerGlowRect...
我們唯一需要添加的步驟就是,用UIGraphicsEndImageContext來儲存映像資訊,並且放到UIImage對象中。
UIImage* backgroundImage = UIGraphicsGetImageFromCurrentImageContext(); // CleanupUIGraphicsEndImageContext();
現在我們已經完成了建立背景圖片的方法,接下來我們必須實現一個通用的初始化方法用來執行個體化Images,並且把他們設定為CBHybird執行個體真正的背景。
- (void)setupBackgrounds { // Generate background images if necessary if (!gBackgroundImage &&!gBackgroundImageHighlighted) { gBackgroundImage = [[self drawBackgroundImageHighlighted:NO] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch]; gBackgroundImageHighlighted = [[self drawBackgroundImageHighlighted:YES] resizableImageWithCapInsets:UIEdgeInsetsMake(borderRadius, borderRadius, borderRadius, borderRadius) resizingMode:UIImageResizingModeStretch]; } // Set background for the button instance [self setBackgroundImage:gBackgroundImage forState:UIControlStateNormal]; [self setBackgroundImage:gBackgroundImageHighlighted forState:UIControlStateHighlighted];}我們可以用custom的類型來執行個體化我們的CBHybird,當然也可以用initWithCoder,如果想用在代碼裡實現的話我們還可以用initWithFrame(···其實這裡我是不想翻譯的,原作者寫得真是太詳細了,第一次翻譯技術文章,還是徹底點吧。。。)
+ (CBHybrid *)buttonWithType:(UIButtonType)type{ return [super buttonWithType:UIButtonTypeCustom];} - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) [self setupBackgrounds]; return self;}為了確保我們建立的BHybird類能正常使用,在Interface Builder裡我們賦值一個button,把實作類別改成CBHybird後,把button的content內容改為CGContext-generated image(便於區分)。是驢是馬,咱們cmd+R跑起來試試。 完整的子類實現代碼在這裡~~~ 【結束語】所有都搞定之後,我們發現用預資源載入繪製仍然比任何一種基於代碼實現的方案表現得更為出色。然而,Core Graphicsis在效率和靈活性上更有優勢。因此,基於兩種技巧的混合方案在現在看來包含了以上兩個的長處,而且在效能上也並沒有明顯的影響。
---Over---