一般計算機在顯示是CPU與GPU協同合做完成一次渲染.接下來咱們瞭解一下CPU/GPU等在這樣一次渲染過程當中,具體的分工是什麼?git
圖片顯示到屏幕上是CPU與GPU的協做完成github
對應應用來講,圖片是最佔用手機內存的資源,將一張圖片從磁盤中加載出來,並最終顯示到屏幕上,中間其實通過了一系列複雜的處理過程。面試
+imageWithContentsOfFile:
方法從磁盤中加載一張圖片,這個時候的圖片並無解壓縮;UIImage
賦值給 UIImageView
;CATransaction
捕獲到了 UIImageView
圖層樹的變化;在主線程的下一個 runloop
到來時,Core Animation
提交了這個隱式的 transaction
,這個過程可能會對圖片進行 copy
操做,而受圖片是否字節對齊等因素的影響,這個 copy
操做可能會涉及如下部分或所有步驟:數組
Core Animation
中CALayer
使用未壓縮的位圖數據渲染 UIImageView
的圖層。渲染流程緩存
咱們提到了圖片的解壓縮是一個很是耗時的 CPU 操做,而且它默認是在主線程中執行的。那麼當須要加載的圖片比較多時,就會對咱們應用的響應性形成嚴重的影響,尤爲是在快速滑動的列表上,這個問題會表現得更加突出。框架
既然圖片的解壓縮須要消耗大量的 CPU 時間,那麼咱們爲何還要對圖片進行解壓縮呢?是否能夠不通過解壓縮,而直接將圖片顯示到屏幕上呢?答案是否認的。要想弄明白這個問題,咱們首先須要知道什麼是位圖ide
其實,位圖就是一個像素數組,數組中的每一個像素就表明着圖片中的一個點。咱們在應用中常常用到的 JPEG 和 PNG 圖片就是位圖函數
你們能夠嘗試oop
UIImage *image = [UIImage imageNamed:@"text.png"]; CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
打印rawData,這裏就是圖片的原始數據.佈局
事實上,不論是 JPEG 仍是 PNG 圖片,都是一種壓縮的位圖圖形格式。只不過 PNG 圖片是無損壓縮,而且支持 alpha 通道,而 JPEG 圖片則是有損壓縮,能夠指定 0-100% 的壓縮比。值得一提的是,在蘋果的 SDK 中專門提供了兩個函數用來生成 PNG 和 JPEG 圖片:
// return image as PNG. May return nil if image has no CGImageRef or invalid bitmap format UIKIT_EXTERN NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image); // return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least) UIKIT_EXTERN NSData * __nullable UIImageJPEGRepresentation(UIImage * __nonnull image, CGFloat compressionQuality);
所以,在將磁盤中的圖片渲染到屏幕以前,必須先要獲得圖片的原始像素數據,才能執行後續的繪製操做,這就是爲何須要對圖片解壓縮的緣由。
既然圖片的解壓縮不可避免,而咱們也不想讓它在主線程執行,影響咱們應用的響應性,那麼是否有比較好的解決方案呢?
咱們前面已經提到了,當未解壓縮的圖片將要渲染到屏幕時,系統會在主線程對圖片進行解壓縮,而若是圖片已經解壓縮了,系統就不會再對圖片進行解壓縮。所以,也就有了業內的解決方案,在子線程提早對圖片進行強制解壓縮。
而強制解壓縮的原理就是對圖片進行從新繪製,獲得一張新的解壓縮後的位圖。其中,用到的最核心的函數是 CGBitmapContextCreate :
CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
NULL
,那麼它應該指向一塊大小至少爲 bytesPerRow * height
字節的內存;若是 爲 NULL
,那麼系統就會爲咱們自動分配和釋放所需的內存,因此通常指定 NULL
便可;width * bytes per pixel
字節。當咱們指定 0/NULL 時,系統不只會爲咱們自動計算,並且還會進行 cache line alignment
的優化kCGImageAlphaPremultipliedFirst
YYCGImageCreateDecodedCopy
存在於 YYImageCoder
類中,核心代碼以下CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) { ... if (decodeForDisplay) { // decode with redraw (may lose some precision) CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask; BOOL hasAlpha = NO; if (alphaInfo == kCGImageAlphaPremultipliedLast || alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaLast || alphaInfo == kCGImageAlphaFirst) { hasAlpha = YES; } // BGRA8888 (premultiplied) or BGRX8888 // same as UIGraphicsBeginImageContext() and -[UIView drawRect:] CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo); if (!context) return NULL; CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode CGImageRef newImage = CGBitmapContextCreateImage(context); CFRelease(context); return newImage; } else { ... } }
它接受一個原始的位圖參數 imageRef
,最終返回一個新的解壓縮後的位圖 newImage
,中間主要通過了如下三個步驟:
CGBitmapContextCreate
函數建立一個位圖上下文;CGContextDrawImage
函數將原始位圖繪製到上下文中;CGBitmapContextCreateImage
函數建立一張新的解壓縮後的位圖。事實上,SDWebImage 中對圖片的解壓縮過程與上述徹底一致,只是傳遞給 CGBitmapContextCreate
函數的部分參數存在細微的差異
性能對比:
SDWebImage
>YYImage
SDWebImage
<YYImage
https://github.com/SDWebImage/SDWebImage
https://github.com/ibireme/YYImage
原文做者:集才華美貌於一身的—C姐