一般計算機在顯示是CPU與GPU協同合做完成一次渲染.接下來咱們瞭解一下CPU/GPU等在這樣一次渲染過程當中,具體的分工是什麼?git
圖片顯示到屏幕上是CPU與GPU的協做完成github
對應應用來講,圖片是最佔用手機內存的資源,將一張圖片從磁盤中加載出來,並最終顯示到屏幕上,中間其實通過了一系列複雜的處理過程。面試
假設咱們使用 +imageWithContentsOfFile:
方法從磁盤中加載一張圖片,這個時候的圖片並無解壓縮;數組
而後將生成的 UIImage
賦值給 UIImageView
;緩存
接着一個隱式的 CATransaction
捕獲到了 UIImageView
圖層樹的變化;bash
在主線程的下一個 runloop
到來時,Core Animation
提交了這個隱式的 transaction
,這個過程可能會對圖片進行 copy
操做,而受圖片是否字節對齊等因素的影響,這個 copy
操做可能會涉及如下部分或所有步驟:框架
Core Animation
中CALayer
使用未壓縮的位圖數據渲染 UIImageView
的圖層。渲染流程ide
咱們提到了圖片的解壓縮是一個很是耗時的 CPU 操做,而且它默認是在主線程中執行的。那麼當須要加載的圖片比較多時,就會對咱們應用的響應性形成嚴重的影響,尤爲是在快速滑動的列表上,這個問題會表現得更加突出。函數
既然圖片的解壓縮須要消耗大量的 CPU 時間,那麼咱們爲何還要對圖片進行解壓縮呢?是否能夠不通過解壓縮,而直接將圖片顯示到屏幕上呢?答案是否認的。要想弄明白這個問題,咱們首先須要知道什麼是位圖oop
其實,位圖就是一個像素數組,數組中的每一個像素就表明着圖片中的一個點。咱們在應用中常常用到的 JPEG 和 PNG 圖片就是位圖
你們能夠嘗試
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
圖片文件只有在確認要顯示時,CPU纔會對齊進行解壓縮.由於解壓是很是消耗性能的事情.解壓過的圖片就不會重複解壓,會緩存起來.
圖片渲染到屏幕的過程: 讀取文件->計算Frame->圖片解碼->解碼後紋理圖片位圖數據經過數據總線交給GPU->GPU獲取圖片Frame->頂點變換計算->光柵化->根據紋理座標獲取每一個像素點的顏色值(若是出現透明值須要將每一個像素點的顏色*透明度值)->渲染到幀緩存區->渲染到屏幕
面試中若是能按照這個邏輯闡述,應該沒有大的問題.不過,若是細問到離屏渲染和渲染中的細節處理.就須要掌握OpenGL ES/Metal 這個2個圖形處理API. 面試過程可能會遇到不在本身技術能力範圍問題,儘可能知之爲知之不知爲不知.