WWDC2018 圖像最佳實踐

Session: WWDC2018 Image and Graphics Best Practicesgit

這個 Session 主要介紹了圖像渲染管線,緩存區,解碼,圖像來源,自定義繪製和離屏繪製。經過學習該 Session,可以對圖像渲染流程有更清晰的認識,同時瞭解如何在開發中提升圖像渲染的性能。github

1. 圖像渲染管線 (Image Rendering Pipeline)

從 MVC 架構的角度來講,UIImage 表明了 Model,UIImageView 表明了 View. 那麼渲染的過程咱們能夠這樣很簡單的表示:swift

Model 負責加載數據,View 負責展現數據。緩存

但實際上,渲染的流程還有一個很重要的步驟:解碼(Decode)。markdown

爲了瞭解Decode,首先咱們須要瞭解Buffer這個概念。網絡

2. 緩衝區 (Buffers)

Buffer 在計算機科學中,一般被定義爲一段連續的內存,做爲某種元素的隊列來使用。架構

下面讓咱們來了解幾種不一樣類型的 Buffer。app

Image Buffers 表明了圖片(Image)在內存中的表示。每一個元素表明一個像素點的顏色,Buffer 大小與圖像大小成正比. async

The frame buffer 表明了一幀在內存中的表示。ide

Data Buffers 表明了圖片文件(Image file)在內存中的表示。這是圖片的元數據,不一樣格式的圖片文件有不一樣的編碼格式。Data Buffers不直接描述像素點。 所以,Decode這一流程的引入,正是爲了將Data Buffers轉換爲真正表明像素點的Image Buffer

所以,圖像渲染管線,其實是像這樣的:

3. 解碼(Decoding)

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。線程的切換是昂貴的,若是同時開十幾,二十個線程來處理,會極大的拖慢處理速度。

4. 圖片來源(Image Sources)

咱們的照片主要有四類來源

  1. Image Assets
  2. Bundle,Framework 裏面的圖片
  3. 在 Documents, Caches 目錄下的圖片
  4. 網絡下載的數據

蘋果官方建議咱們儘量地使用 Image Assets, 由於蘋果作了不少相關優化,好比緩存,每一個設備只打包本身使用到的圖片,從 iOS11 開始也支持了無損放大的矢量圖。

5. 自定義繪製 (Custom Drawing)

只須要記住一個準則便可。除非萬不得已,不要重載drawRect函數。

由於重載drawRect函數會致使系統給UIView建立一個backing store, 像素的數量是UIView大小乘以 contentsScale 的值。所以會耗費很多內存。

UIView 並非必定須要一個backing store的,好比設置背景顏色就不須要(除非是 pattern colors)。若是須要設置複雜的背景顏色,能夠直接經過 UIImageView 來實現。

6. 離屏繪製(Drawing Off-Screen)

若是咱們想要本身建立Image Buffers, 咱們一般會選擇使用UIGraphicsBeginImageContext(), 而蘋果的建議是使用UIGraphicsImageRenderer,由於它的性能更好,還支持廣色域。

總結

這個 Session 的時長只有三十多分鐘,所以主要從宏觀角度爲咱們介紹了圖像相關的知識。咱們能夠對它的每個小章節都繼續深挖。

對圖形性能有興趣的同窗,能夠更深刻地學習 WWDC 2014的 《Advanced Graphics and Animations for iOS Apps》。以前編寫的這篇WWDC心得與延伸:iOS圖形性能 也是很好的學習資料。

相關文章
相關標籤/搜索