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图形性能 也是很好的学习资料。

相关文章
相关标签/搜索