當一個產品漸漸成熟,咱們便開始重視產品性能的優化。而這其中圖形性能的優化在iOS客戶端佔比較重要的部分。這裏咱們將介紹Core Animation的運行機制,首先咱們不要被它的名字誤導了,Core Animation不是隻用來作動畫的,iOS視圖的顯示都是經過它來完成的,因此咱們想要優化圖形性能必須瞭解Core Animation。下面咱們根據蘋果WWDC視頻講解來認識Core Animation
工做機制,據此分析具體卡頓的緣由,如何避免這些問題形成的卡頓,而且結合實際狀況說明從哪些方面優化能夠事半功倍。html
Core Animation
在App將圖層數據提交到應用外進程Render Server,這是
Core Animation
的服務端,把數據解碼成GPU可執行的指令交給GPU執行。能夠看出一個問題渲染服務並非在App進程內進行的,也就是說渲染部分咱們沒法進行優化,咱們能夠優化的點只能在第一個提交事務的階段。那麼這個階段
Core Animation
到底作了什麼呢?下面咱們一塊兒來看看!
提交事務分爲四個階段:佈局、顯示、準備、提交。 ios
根據上面咱們所提到4個階段,咱們看看哪些因素會影響到App的性能,而且如何優化能夠提升咱們App的性能。緩存
平時咱們寫代碼的時候,每每會給不一樣的CALayer添加不一樣的顏色,不一樣的透明度,咱們最後看到是全部這些層CALayer混合出的結果。bash
那麼在iOS中是如何進行混合的?前面咱們說明了每一個像素都包含了R(紅)、G(綠)、B(藍)和R(透明度),GPU要計算每一個像素混合來的RGB值。那麼如何計算這些顏色的混合值呢?假設在正常混合模式下,而且是像素對齊的兩個CALayer,混合計算公式以下:網絡
R = S + D * ( 1 – Sa )
複製代碼
蘋果的文檔中有對每一個參數的解釋:session
The blend mode constants introduced in OS X v10.5 represent the Porter-Duff blend modes. The symbols in the equations for these blend modes are:
* R is the premultiplied result
* S is the source color, and includes alpha
* D is the destination color, and includes alpha
* Ra, Sa, and Da are the alpha components of R, S, and D
複製代碼
R就是獲得的結果色,S和D是包含透明度的源色和目標色,其實就是預先乘以透明度後的值。Sa就是源色的透明度。iOS爲咱們提供了多種的Blend mode:多線程
/* Available in Mac OS X 10.5 & later. R, S, and D are, respectively,
premultiplied result, source, and destination colors with alpha; Ra,
Sa, and Da are the alpha components of these colors.
The Porter-Duff "source over" mode is called `kCGBlendModeNormal':
R = S + D*(1 - Sa)
Note that the Porter-Duff "XOR" mode is only titularly related to the
classical bitmap XOR operation (which is unsupported by
CoreGraphics). */
kCGBlendModeClear, /* R = 0 */
kCGBlendModeCopy, /* R = S */
kCGBlendModeSourceIn, /* R = S*Da */
kCGBlendModeSourceOut, /* R = S*(1 - Da) */
kCGBlendModeSourceAtop, /* R = S*Da + D*(1 - Sa) */
kCGBlendModeDestinationOver, /* R = S*(1 - Da) + D */
kCGBlendModeDestinationIn, /* R = D*Sa */
kCGBlendModeDestinationOut, /* R = D*(1 - Sa) */
kCGBlendModeDestinationAtop, /* R = S*(1 - Da) + D*Sa */
kCGBlendModeXOR, /* R = S*(1 - Da) + D*(1 - Sa) */
kCGBlendModePlusDarker, /* R = MAX(0, (1 - D) + (1 - S)) */
kCGBlendModePlusLighter /* R = MIN(1, S + D) */
複製代碼
彷佛計算也不是很複雜,可是這只是一個像素覆蓋另外一個像素簡單的一步計算,而正常狀況咱們現實的界面會有很是多的層,每一層都會有百萬計的像素,這都要GPU去計算,負擔是很重的。架構
像素對齊就是視圖上像素和屏幕上的物理像素完美對齊。上面咱們說混合的時候,假設的狀況是多個layer是在每一個像素都徹底對齊的狀況下來進行計算的,若是像素不對齊的狀況下,GPU須要進行Anti-aliasing反抗鋸齒計算,GPU的負擔就會加劇。像素對齊的狀況下,咱們只須要把全部layer上的單個像素進行混合計算便可。併發
那麼什麼緣由形成像素不對齊?主要有兩點:app
上面咱們說過一個混合計算的公式:
R = S + D * ( 1 – Sa )
複製代碼
若是Sa值爲1,也就是源色對應的像素不透明。那麼獲得R = S
,這樣就只須要拷貝最上層的layer,不須要再進行復雜的計算了。由於下面層的layer所有是可不見的,因此GPU無需進行混合計算了。如何讓GPU知道這個圖像是不透明的呢?若是使用的是CALayer,那麼要把opaque屬性設置成YES(默認是NO)。而若只用的是UIView,opaque默認屬性是YES。當GPU知道是不透明的時候,只會作簡單的拷貝工做,避免了複雜的計算,大大減輕了GPU的工做量。
若是加載一個沒有alpha通道的圖片,opaque屬性會自動設置爲YES。可是若是是一個每一個像素alpha值都爲100%的圖片,儘管此圖不透明可是Core Animation
依然會假定是否存在alpha值不爲100%的像素。
上一篇文章咱們有說到,通常在Core Animation準備階段,會對圖片進行解碼操做,即把壓縮的圖像解碼成位圖數據。這是一個很消耗CPU的事情。系統是在圖片將要渲染到屏幕以前再進行解碼,並且默認是在主線程中進行的。因此咱們能夠將解碼放在子線程中進行,下面簡單列舉一種解碼方式:
NSString *picPath = [[NSBundle mainBundle] pathForResource:@"tests" ofType:@"png"];
NSData *imageData = [NSData dataWithContentsOfFile:picPath];//讀取未解碼圖片數據
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge CFTypeRef)imageData, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSourceRef, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)});
CFRelease(imageSourceRef);
size_t width = CGImageGetWidth(imageRef);//獲取圖片寬度
size_t height = CGImageGetHeight(imageRef);//獲取圖片高度
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);//每一個顏色組件佔的bit數
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);//每一個像素佔幾bit
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);//位圖數據每行佔多少bit
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
CFRelease(imageRef);
CFDataRef dataRef = CGDataProviderCopyData(dataProvider);//得到解碼後數據
CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(dataRef);
CFRelease(dataRef);
CGImageRef newImageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault);
CFRelease(newProvider);
UIImage *image = [UIImage imageWithCGImage:newImageRef scale:2.0 orientation:UIImageOrientationUp];
CFRelease(newImageRef);
複製代碼
另外,在iOS7以後蘋果提供了一個屬性kCGImageSourceShouldCacheImmediately
,在CGImageSourceCreateImageAtIndex
方法中,設置kCGImageSourceShouldCacheImmediately
爲kCFBooleanTrue
的話能夠馬上開始解壓縮,默認爲kCFBooleanFalse
。固然也像AFNetworking 中使用void CGContextDrawImage(CGContextRef __nullable c, CGRect rect, CGImageRef __nullable image)
方法也能夠實現解碼,具體實現不在此贅述。
咱們前面說像素對齊時,簡單介紹了字節對齊。那麼到底什麼是字節對齊?爲何要字節對齊?和咱們優化圖形性能有什麼關係呢?
字節對齊是對基本數據類型的地址作了一些限制,即某種數據類型對象的地址必須是其值的整數倍。例如,處理器從內存中讀取一個8個字節的數據,那麼數據地址必須是8的整數倍。
對齊是爲了提升讀取的性能。由於處理器讀取內存中的數據不是一個一個字節讀取的,而是一塊一塊讀取的通常叫作cache lines
。若是一個不對齊的數據放在了2個數據塊中,那麼處理器可能要執行兩次內存訪問。當這種不對齊的數據很是多的時候,就會影響到讀取性能了。這樣可能會犧牲一些儲存空間,可是對提高了內存的性能,對現代計算機來講是更好的選擇。
在iOS中,若是這個圖像的數據沒有字節對齊,那麼Core Animation會自動拷貝一份數據作對齊處理。這裏咱們能夠提早作好字節對齊。在方法CGBitmapContextCreate(void * __nullable data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef __nullable space, uint32_t bitmapInfo)
中,有一個參數bytesPerRow,意思是指定要使用的位圖每行內存的字節數,ARMv7架構的處理器的cache lines是32byte,A9處理器的是64byte,這裏咱們要使bytesPerRow爲64的整數倍。具體能夠參考官方文檔Quartz 2D Programming Guide和WWDC 2012 Session 238 "iOS App Performance: Graphics and Animations"。字節對齊,在通常狀況下,感受對性能的影響很小,不必的狀況不要過早優化。
離屏渲染(Off-Screen Rendering)是指GPU在當前屏幕緩衝區之外新開闢一個緩衝區進行渲染操做。離屏渲染是很消耗性能的,由於首先要建立屏幕外緩衝區,還要進行兩次上下文環境切換。先切換到屏幕外環境,離屏渲染完成後再切換到當前屏幕,上下文的切換是很高昂的消耗。產生離屏渲染的緣由就是這些圖層不能直接繪製在屏幕上,必須進行預合成。
產生離屏渲染的狀況大概有幾種: 1.cornerRadius
和masksToBounds
(UIView中是clipToBounds
)一塊兒使用的時候,單獨使用不會觸發離屏渲染。cornerRadius
只對背景色起做用,因此有contents的圖層須要對其進行裁剪。 2.爲圖層設置mask(遮罩)。 3.layer的allowsGroupOpacity
屬性爲YES且opacity
小於1.0,GroupOpacity是指子圖層的透明度值不能大於父圖層的。 4.設置了shadow(陰影)。
上面這幾種狀況都是GPU的離屏渲染,還有一種特殊的CPU離屏渲染。只要實現Core Graphics繪製API會產生CPU的離屏渲染。由於它也不是直接繪製到屏幕上的,並且先建立屏幕外的緩存。
咱們如何解決這幾個產生離屏渲染的問題呢?首先,GroupOpacity對性能幾乎沒有影響,在此就很少說了。圓角是一個沒法避免的,網上有不少例子是用Core Graphics繪製來代替系統圓角的,可是Core Graphics是一種軟件繪製,利用的是CPU,性能上要差上很多。固然在CPU利用率不是很高的界面是個不錯的選擇,可是有時候某個界面可能須要CPU去作其餘消耗很大的事情,如網絡請求。這個時候時候在用Core Graphics繪製大量的圓角圖形就有可能出現掉幀。這種狀況怎麼辦呢?最好的就是設計師直接提供圓角圖像。還有一種折中的方法就是在混合圖層,在原圖層上覆蓋一個你要的圓角形狀的圖層,中間須要顯示的部分是透明的,覆蓋的部分和周圍背景一致。
對於shadow,若是圖層是個簡單的幾何圖形或者圓角圖形,咱們能夠經過設置shadowPath
來優化性能,能大幅提升性能。示例以下:
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
複製代碼
咱們還能夠經過設置shouldRasterize
屬性值爲YES來強制開啓離屏渲染。其實就是光柵化(Rasterization)。既然離屏渲染這麼很差,爲何咱們還要強制開啓呢?當一個圖像混合了多個圖層,每次移動時,每一幀都要從新合成這些圖層,十分消耗性能。當咱們開啓光柵化後,會在首次產生一個位圖緩存,當再次使用時候就會複用這個緩存。可是若是圖層發生改變的時候就會從新產生位圖緩存。因此這個功能通常不能用於UITableViewCell中,cell的複用反而下降了性能。最好用於圖層較多的靜態內容的圖形。並且產生的位圖緩存的大小是有限制的,通常是2.5個屏幕尺寸。在100ms以內不使用這個緩存,緩存也會被刪除。因此咱們要根據使用場景而定。
上面咱們說了這麼多性能相關的因素,那麼咱們怎麼進行性能的測試,怎麼知道哪些因素影響了圖形性能?蘋果很人性得爲咱們提供了一個測試工具Instruments。能夠在Xcode->Open Develeper Tools->Instruments中找到,咱們看到這裏面有不少的測試工具,像你們可能經常使用的檢測內存泄漏的Leaks,在這裏咱們就討論下Core Animation這個工具的使用。
Core Animation工具用來監測Core Animation性能。提供可見的FPS值。而且提供幾個選項來測量渲染性能,下面咱們來講明每一個選項的能: Color Blended Layers:這個選項若是勾選,你能看到哪一個layer是透明的,GPU正在作混合計算。顯示紅色的就是透明的,綠色就是不透明的。
Color Hits Green and Misses Red:若是勾選這個選項,且當咱們代碼中有設置shouldRasterize爲YES,那麼紅色表明沒有複用離屏渲染的緩存,綠色則表示複用了緩存。咱們固然但願可以複用。
Color Copied Images:按照官方的說法,當圖片的顏色格式GPU不支持的時候,即不是32bit的顏色格式,Core Animation會 拷貝一份數據讓CPU進行轉化。例如從網絡上下載了8bit的顏色格式的圖片,則須要CPU進行轉化,這個區域會顯示成藍色。還有一種狀況會觸發Core Animation的copy方法,就是字節不對齊的時候。
Color Misaligned Images:勾選此項,若是圖片須要縮放則標記爲黃色,若是沒有像素對齊則標記爲紫色。像素對齊咱們已經在上面有所介紹。
Color Offscreen-Rendered Yellow:用來檢測離屏渲染的,若是顯示黃色,表示有離屏渲染。固然還要結合Color Hits Green and Misses Red
來看,是否複用了緩存。
Color OpenGL Fast Path Blue:這個選項對那些使用OpenGL的圖層纔有用,像是GLKView或者 CAEAGLLayer,若是不顯示藍色則表示使用了CPU渲染,繪製在了屏幕外,顯示藍色表示正常。
Flash Updated Regions:當對圖層重繪的時候回顯示黃色,若是頻繁發生則會影響性能。能夠用增長緩存來加強性能。官方文檔Improving Drawing Performance有所說明。
結合前面兩章內容,咱們發現,一個簡單的圖片顯示在屏幕上,要通過不少步驟,而且有許多硬件的參與。最主要的就是CPU和GPU,協調他們之間的工做是高性能得關鍵。
由於圖形的性能和二者都有關係,CPU主要負責軟解碼、I/O相關、佈局的計算等工做,若是使用Core Graphics繪圖API那麼也會用到CPU。GPU的主要責任就是合成渲染。爲了可以獲得最好的性能,咱們就要找出是哪一個限制了性能,CPU過分利用仍是GPU負擔太大。經過蘋果給出的Instruments裏面的測試工具,咱們在真機上一次次的測試,才能正確的判斷出沒法保證畫面60FPS的緣由。必須平衡二者,才能達到最好的性能。
下面咱們總結幾個優化點: 1.儘可能使用iOS優化處理的圖片格式,減小CPU軟解碼的負擔。 2.能不透明的不要使用透明度,減小混合計算。 3.不要讓圖層過於複雜,否則增長了處理圖層,打包傳送到渲染服務的工做量,GPU渲染負擔也會增大。 4.最好不要使用離屏渲染,必須使用的話最好可以複用緩存,離屏渲染對性能影響是最大的。 5.佈局不要過於複雜,若是必需要複雜的佈局,能夠提早緩存佈局數據。 6.不要濫用多線程,由於建立和銷燬線程不只增長CPU任務量,並且會消耗內存。
最後須要說明的就是不要過早和過分得優化,過猶不及。過早優化得不償失,反而耗時耗力。過分優化有時候拔苗助長。
視頻參考:WWDC 2015’s session 233:Advanced Touch Input on iOS
文檔參考:objccn