歡迎光臨
每天分享高質量文章

iOS性能優化——圖片加載和處理

作者:落影loyinglin

鏈接:https://www.jianshu.com/p/7d8a82115060

前言

本文基於WWDC2018-Image and Graphics Best Practices:https://developer.apple.com/videos/play/wwdc2018/219/,對圖片加載和處理的思考和總結。


本文不是WWDC翻譯,如果需要瞭解視頻內容可以點擊上面的鏈接觀看。

正文

圖片的顯示分為三步:加載、解碼、渲染。

通常,我們操作的只有加載,解碼和渲染是由UIKit進行。


什麼是解碼?

以UIImageView為例。當其顯示在屏幕上時,需要UIImage作為資料源。
UIImage持有的資料是未解碼的壓縮資料,能節省較多的記憶體和加快儲存。
當UIImage被賦值給UIImage時(例如
imageView.image = image;),圖像資料會被解碼,變成RGB的顏色資料。
解碼是一個計算量較大的任務,且需要CPU來執行;並且解碼出來的圖片體積與圖片的寬高有關係,而與圖片原來的體積無關。
其體積大小可簡單描述為:寬 * 高 * 每個像素點的大小 = width * height * 4bytes。


圖像解碼操作會造成什麼問題?

以我們常見的UITableView和UICollectionView為例,假如我們在使用一個多圖片顯示的功能:


在上下滑動顯示圖片的過程中,我們會在cellFor的方法加載UIImage圖片、賦值給UIImageView,相當於在主執行緒同時進行IO操作、解碼操作等,會造成記憶體迅速增長和CPU負載瞬間提升。
並且記憶體的迅速增加會觸發系統的記憶體回收機制,嘗試回收其他後臺行程的記憶體,增加CPU的工作量。如果系統無法提供足夠的記憶體,則會先結束其他後臺行程,最終無法滿足的話會結束當前行程。



那麼如何對這種情況進行優化 ?

優化1:降採樣

在滑動顯示的過程中,圖片顯示的寬高遠比真實圖片要小,我們可以採用加載縮略圖的方式減少圖片的占用記憶體。

如下圖所示:


我們加載jpeg的圖片,然後進行相關設置,解碼後根據設置生成CGImage縮略圖,最後包裝成UIImage,最終傳遞給UIImageView渲染。


思考:這裡的解碼步驟為何不是上文提到的imageView.image=image時機?


func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let imageSourceOptions = [kCGImageSourceShouldCache: falseas CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}


我的理解:正常的UIImage加載是從APP本地讀取,或者從網絡下載圖片,此時不涉及圖片內容相關的操作,並不需要解碼;當圖片被賦值給UIImageView時,CALayer讀取圖片內容進行渲染,所以需要對圖片進行解碼;
而上文的縮略圖生成過程中,已經對圖片進行解碼操作,此時的UIImage只是一個CGImage的封裝,所以當UIImage賦值給UIImageView時,CALayer可以直接使用CGImage所持有的圖像資料。

優化2:異步處理


從用戶的體驗來分析,滑動的操作往往是間斷性觸發,在滑動的瞬間有較大的工作量,而且由於都是在主執行緒進行操作無法進行任務分配,CPU 2處於閑置。由此引申出兩種優化手段:Prefetching(預處理)和
Background decoding/downsampling(子執行緒解碼和降採樣)。綜合起來,可以在Prefetching的時候把降採樣放到子執行緒進行處理,因為降採樣過程就包括解碼操作。



Prefetching回呼中,把降採樣的操作放到同步佇列serialQueue中,處理完畢之後拋給主執行緒進行update操作。
需要特別註意,此處不能是異步佇列,否則會造成執行緒爆炸,原因見總結部分。


優化3:使用Image Asset Catalogs

Apple推薦的圖片資源管理工具,壓縮效率更高,在iOS 12的機器上有10~20%的空間節約,並且每個版本Apple都會持續對其進行優化。
內容較多,詳細可點Session:
https://developer.apple.com/videos/play/wwdc2018/227/

總結

應用上述的優化策略,已經能對圖片加載有比較好的優化。
WWDC後續還有對CustomDrawing和CALayer的BackingStore的介紹,因為與圖片關係不大,不在此贅述。


下麵再介紹我對WWDC學習的看法。

附錄

我們可以先主觀假設兩個前提:


1、大部分蘋果工程師對iOS系統內部實現都比我們要清楚;
2、能到WWDC分享的工程師在蘋果內部也是優秀的工程師;


那麼WWDC所講的內容我們可以認為是事實上的結果。
於是可以使用我們所掌握的基礎知識,還有對iOS系統的瞭解來分析WWDC上面所提到的現象,看我們的iOS知識體系是否存在缺陷;另外,WWDC介紹的很多知識點同樣免驗證的加入自己的知識體系。
這就是我比較喜歡的一種看WWDC視頻的學習方式。
以上文提到的執行緒爆炸為例,看看這種方式的好處。


原文如下:
Thread Explosion(執行緒爆炸)
More images to decode than available CPUs(解碼圖像數量大於CPU數量)
GCD continues creating threads as new work is enqueued(GCD創建新執行緒處理新的任務)
Each thread gets less time to actually decode images(每個執行緒獲得很少的時間解碼圖像)


從這個案例我們學習到如何避免圖像解碼的執行緒爆炸,但還能擴散思維:


我們分析蘋果工程師的邏輯:
原因(解碼任務過多)==> 過程(GCD開啟更多執行緒) ==> 結果( 每個執行緒獲得更少的時間)
延伸出來的問題有:
GCD是如何處理異步佇列?為何會啟動多個執行緒處理?
多少的執行緒數量是合適的?執行緒的cpu時間分配和切換代價如何?

舉一反三,類似的問題太多。但是這樣的思考稍顯混亂,仍有優化的空間。


把腦海關於GCD的認知提煉出來:
1、GCD是用來處理一系列任務的同步和異步執行,佇列有串行和併發兩種,與執行緒的關係只有主執行緒和非主執行緒的區別;
2、串行佇列是執行完當前的任務,才會執行下一個block任務;並行佇列是多個block任務並行執行,GCD會根據任務的執行情況分配執行緒,原則是儘快完成所有任務;


接下來的表現是操作系統相關的知識:
1、iOS系統中行程和執行緒的關聯,每個啟動的APP都是一個行程,其中有多個執行緒;
2、cpu的時間是分為多個時間片,每個執行緒輪詢執行;
3、執行緒切換執行有代價,但比行程切換小得多;
4、每個cpu核心在同一時刻只能執行一個執行緒;


至此我們可以結合操作系統和GCD的知識,猜測底層GCD的實現思路和執行緒爆炸情況下的表現:


主執行緒把多個任務block放到併發佇列,GCD先啟動一個執行緒處理解碼任務,執行緒執行過程中遇到耗時操作時(IO等待、大量CPU計算),短時間內無法完成,為了不阻塞後續任務的執行,GCD啟動新的執行緒處理新的任務。


集合此案例,我們能回答相關問題:
1、現在有一個很複雜的計算任務,例如是統計一個5000×5000圖片中像素點的RGB顏色通道,如果用分為25個任務放到GCD併發佇列,把大圖切分成25個1000×1000小圖分別統計,是否會速度的提升?
2、GCD的串行佇列和併發佇列的應用場景有何不同?

以上一些平時學習的感受。


如果能對你有所觸動,十分榮幸;
如果你覺得能改進,歡迎提出來幫助我成長;
如果你覺得毫無用處,至少你知道一種錯誤的學習方法。



編號312,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂