Session: WWDC2018 Image and Graphics Best Practicesgit
這個 Session 主要介紹了圖像渲染管線,緩存區,解碼,圖像來源,自定義繪製和離屏繪製。經過學習該 Session,可以對圖像渲染流程有更清晰的認識,同時瞭解如何在開發中提升圖像渲染的性能。github
從 MVC 架構的角度來講,UIImage
表明了 Model,UIImageView
表明了 View. 那麼渲染的過程咱們能夠這樣很簡單的表示:swift
Model 負責加載數據,View 負責展現數據。緩存
但實際上,渲染的流程還有一個很重要的步驟:解碼(Decode)。markdown
爲了瞭解Decode
,首先咱們須要瞭解Buffer
這個概念。網絡
Buffer
在計算機科學中,一般被定義爲一段連續的內存,做爲某種元素的隊列來使用。架構
下面讓咱們來了解幾種不一樣類型的 Buffer。app
Image Buffers
表明了圖片(Image)在內存中的表示。每一個元素表明一個像素點的顏色,Buffer 大小與圖像大小成正比. async
The frame buffer
表明了一幀在內存中的表示。ide
Data Buffers
表明了圖片文件(Image file)在內存中的表示。這是圖片的元數據,不一樣格式的圖片文件有不一樣的編碼格式。Data Buffers
不直接描述像素點。 所以,Decode
這一流程的引入,正是爲了將Data Buffers
轉換爲真正表明像素點的Image Buffer
所以,圖像渲染管線,其實是像這樣的:
將Data Buffers
解碼到 Image Buffers
是一個CPU密集型的操做。同時它的大小是和與原始圖像大小成比例,和 View 的大小無關。
想象一下,若是一個瀏覽照片的應用展現多張照片時,沒有通過任何處理,就直接讀取圖片,而後來展現。那 Decode 時,將會佔用極大的內存和 CPU。而咱們展現的圖片的 View 的大小,實際上是徹底用不到這麼大的原始圖像的。
如何解決這種問題呢? 咱們能夠經過 Downsampling
來解決,也便是生成縮略圖的方式。
咱們能夠經過這段代碼來實現:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage { //生成CGImageSourceRef 時,不須要先解碼。 let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)! let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale //kCGImageSourceShouldCacheImmediately //在建立Thumbnail時直接解碼,這樣就把解碼的時機控制在這個downsample的函數內 let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary //生成 let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)! return UIImage(cgImage: downsampledImage) }複製代碼
經過Downsampling
,咱們成功地減低了內存的使用,可是解碼一樣會耗費大量的 CPU 資源。若是用戶快速滑動界面,頗有可能由於解碼而形成卡頓。
解決辦法:Prefetching
+ Background decoding
Prefetch 是 iOS10 以後加入到 TableView 和 CollectionView 的新技術。咱們能夠經過tableView(_:prefetchRowsAt:)
這樣的接口提早準備好數據。有興趣的小夥伴能夠搜一下相關知識。
至於Background decoding
其實就是在子線程處理好解碼的操做。
let serialQueue = DispatchQueue(label: "Decode queue") func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { // Asynchronously decode and downsample every image we are about to show for indexPath in indexPaths { serialQueue.async { let downsampledImage = downsample(images[indexPath.row]) DispatchQueue.main.async { self.update(at: indexPath, with: downsampledImage) } } }複製代碼
值得注意的是,上面用了一條串行隊列來處理,這是爲了不Thread Explosion
。線程的切換是昂貴的,若是同時開十幾,二十個線程來處理,會極大的拖慢處理速度。
咱們的照片主要有四類來源
蘋果官方建議咱們儘量地使用 Image Assets
, 由於蘋果作了不少相關優化,好比緩存,每一個設備只打包本身使用到的圖片,從 iOS11 開始也支持了無損放大的矢量圖。
只須要記住一個準則便可。除非萬不得已,不要重載drawRect
函數。
由於重載drawRect
函數會致使系統給UIView建立一個backing store
, 像素的數量是UIView大小乘以 contentsScale 的值。所以會耗費很多內存。
UIView 並非必定須要一個backing store
的,好比設置背景顏色就不須要(除非是 pattern colors)。若是須要設置複雜的背景顏色,能夠直接經過 UIImageView 來實現。
若是咱們想要本身建立Image Buffers
, 咱們一般會選擇使用UIGraphicsBeginImageContext()
, 而蘋果的建議是使用UIGraphicsImageRenderer
,由於它的性能更好,還支持廣色域。
這個 Session 的時長只有三十多分鐘,所以主要從宏觀角度爲咱們介紹了圖像相關的知識。咱們能夠對它的每個小章節都繼續深挖。
對圖形性能有興趣的同窗,能夠更深刻地學習 WWDC 2014的 《Advanced Graphics and Animations for iOS Apps》。以前編寫的這篇WWDC心得與延伸:iOS圖形性能 也是很好的學習資料。