1. 程式人生 > >ios-Swift/Object C開發 稽核上架交流

ios-Swift/Object C開發 稽核上架交流

前言

本文基於WWDC2018-Image and Graphics Best Practices,對圖片載入和處理的思考和總結。

本文不是WWDC翻譯,如果需要了解視訊內容可以點選上面的連結觀看。

正文

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

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

image.png

什麼是解碼?

以UIImageView為例。當其顯示在螢幕上時,需要UIImage作為資料來源。

UIImage持有的資料是未解碼的壓縮資料,能節省較多的記憶體和加快儲存。

當UIImage被賦值給UIImage時(例如imageView.image = image;),影象資料會被解碼,變成RGB的顏色資料。

解碼是一個計算量較大的任務,且需要CPU來執行;並且解碼出來的圖片體積與圖片的寬高有關係,而與圖片原來的體積無關。

其體積大小可簡單描述為:寬 * 高 * 每個畫素點的大小 = width * height * 4bytes。

image.png

影象解碼操作會造成什麼問題?

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

image.png

在上下滑動顯示圖片的過程中,我們會在cellFor的方法載入UIImage圖片、賦值給UIImageView,相當於在主執行緒同時進行IO操作、解碼操作等,會造成記憶體迅速增長和CPU負載瞬間提升。

並且記憶體的迅速增加會觸發系統的記憶體回收機制,嘗試回收其他後臺程序的記憶體,增加CPU的工作量。如果系統無法提供足夠的記憶體,則會先結束其他後臺程序,最終無法滿足的話會結束當前程序。

image.png

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

優化1:降取樣

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

如下圖所示:

image.png

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

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

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

我的理解:正常的UIImage載入是從APP本地讀取,或者從網路下載圖片,此時不涉及圖片內容相關的操作,並不需要解碼;當圖片被賦值給UIImageView時,CALayer讀取圖片內容進行渲染,所以需要對圖片進行解碼;

而上文的縮圖生成過程中,已經對圖片進行解碼操作,此時的UIImage只是一個CGImage的封裝,所以當UIImage賦值給UIImageView時,CALayer可以直接使用CGImage所持有的影象資料。

優化2:非同步處理

image.png

從使用者的體驗來分析,滑動的操作往往是間斷性觸發,在滑動的瞬間有較大的工作量,而且由於都是在主執行緒進行操作無法進行任務分配,CPU 2處於閒置。由此引申出兩種優化手段:Prefetching(預處理)和

Background decoding/downsampling(子執行緒解碼和降取樣)。綜合起來,可以在Prefetching的時候把降取樣放到子執行緒進行處理,因為降取樣過程就包括解碼操作。

image.png

Prefetching回撥中,把降取樣的操作放到同步佇列serialQueue中,處理完畢之後拋給主執行緒進行update操作。

需要特別注意,此處不能是非同步佇列,否則會造成執行緒爆炸,原因見總結部分。

image.png

優化3:使用Image Asset Catalogs

Apple推薦的圖片資源管理工具,壓縮效率更高,在iOS 12的機器上有10~20%的空間節約,並且每個版本Apple都會持續對其進行優化。

內容較多,詳細可點Session。

總結

應用上述的優化策略,已經能對圖片載入有比較好的優化。

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的序列佇列和併發佇列的應用場景有何不同?

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

如果能對你有所觸動,十分榮幸;

如果你覺得能改進,歡迎提出來幫助我成長;

如果你覺得毫無用處,至少你知道一種錯誤的學習方法。