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