Image and Graphics Best Practices
目录
1.UIImage and UIImageView
2.Custom drawing with UIKit
3.Advanced CPU and GPU
4.总结
1. UIImage and UIImageView
1.1 渲染的过程
在经典的MVC框架中,UIImage扮演Model的角色,UIImageView则充当View,UIImage用于加载图片内容,UIImageView负责将图片内容渲染和显示,看似单向逻辑关系,但实际上为了衡量应用程序的性能,我们必须关注隐藏的Decode环节,如下图所示。但在讨论Decode环节前,我们得先明确buffers的概念。
1.2 Buffers
imagebuffer中的每一个元素描述的是一个像素的颜色信息,buffer的size与图片的size为正相关关系。imagebuffer的一个典型例子便是framebuffer,framebuffer真正管理着屏幕的输出。
在应用程序更新图层时,UIKit将window及其subviews渲染至framebuffer,这个framebuffer提供每个像素的信息以供显示硬件定时读取,读取的频率一般为60Hz,但在ipad上可提升至120Hz。
与imagebuffer不同,Data Buffer存储着一个图片文件的内容,通常为JPG,PNG等压缩格式,因此其内容不是直接与像素的信息一一对应。metadata存储着图片的尺寸。
1.3 Decode过程
明确了buffers的概念后,我们便理解了Decode环节存在的必要性。如上图所示,在实际的渲染过程中,UIImage负责解压Data Buffer内容并申请buffer(Image Buffer)存储解压后的图片信息,然后UIImageView负责将Image Buffer 拷贝至 framebuffer,用于给显示硬件提供颜色信息。
解压过程是一个大量占用CPU资源的工作,因此UIImage 会retain存储解压后信息的Image Buffer以便给重复的渲染工作提供信息,Image Buffer与图片的实际尺寸有关(理论值为height * width * 4 bytes),与图片文件大小无关。若是在TableView等列表中连续加载多张图片,便会引发连续的大块内存分配,这将对Memory和CPU带来沉重的负担。
1.4 DownSampling
DownSampling即在Decode前插入创建缩略图的过程。如下图所示,即对image进行预处理,采用缩小解码后Image Buffer的size的方式,减少内存的占用。此处需注意两个选项的设置。
1.kCGImageSourceShouldCache此处设为false,目的在于告知系统此imageSource仅为建立与imageURL的链接,不需要解码。
2.kCGImageSourceShouldCacheImmediately设为true,以此控制系统以计算出的size创建Image Buffer并解压。
经过此预处理过程后,内存占用的前后对比如下:
但正如Decode部分的介绍,解压是一个CPU密集型工作,如果在主线程中做同步解压,会导致主线程卡顿,影响用户的使用体验。因此使用Downsampling的正确姿势如下:
此处通过两种手段解决主线程卡顿的问题:
1. 预取.
2. 异步解码.
异步解码需注意,不用通过dispatch至global queue中,可能会导致 thread explosion的问题。
1.5 Image Source
推荐使用Image Assets,主要有如下几点原因:
1.对基于名字和特性的查找做了优化.
2.更加智能的缓存策略.
3.针对设备选择相应的图片.
4.支持矢量图.
从iOS11之后,Image Assets支持 Preserve Vector Data 选项,系统会在compile过程保留矢量图信息,以防止图片渲染至超出或小于自身尺寸的view中引发的模糊问题。
2. Custom drawing with UIKit
2.1 Custom drawing
以如下live按钮的实现为例:
一解决方案如下:
在说明这种解决方案的优劣前,我们先来说明重写draw:函数后,系统渲染的过程:
众所周知,UIView背后的CALayer负责将contents传递给framebuffer,当重写draw:函数后,CALayer会负责创建一个Backing Store(尺寸与view显示的size成正相关),然后在此Backing store上执行draw:函数,最后将其中的内容传递给framebuffer。
我们可以通过设置CALayer的contentsFormat(指明是否需要支持wide range color)来控制Backing Store中每个元素的大小,但在iOS12中,系统对wide range color做了优化,如需要,可自动增加buffer的size以支持。
因此为了减少Backing Store的使用,针对live按钮的实现,我们应该将大的view化为多个subview的组合,减少draw:函数的使用减少imageData的多重拷贝(在给出的解决方案中,会将image ‘draw at’ Backing store,多了一次imageData的拷贝动作),使用优化的选项和属性(UIView的backgroundColor属性可以不通过Backing store直接渲染至framebuffer。但pattern color例外,可用添加imageview为subview代替)。
2.2 背景的实现
UIView的maskView 及CALayer.maskLayer都会将图层渲染到临时的image buffer中,而CALayer.cornerRadius不需要。对于live按钮的背景实现,不要用maskView,以UIImageView+Resizable的方式解决。
2.3 图片的实现
UIImageView在渲染时可直接对单色图片着色,设置tint color来改变单色图片的颜色从而达到复用图片的目的。
2.4 文字的实现
UILabel对单色的string做了优化处理,可节省75%的Backing Store,并能自动更新Backing Store的size以适配富文本或emoji。
至此,live按钮的实现方案应如下。
3. Advanced CPU and GPU
此sessions对这一部分仅做了简单的描述。
3.1 Advanced Image Effects
- 对图片进行实时处理时考虑使用Core Image
- 使用GPU处理,解放CPU
- UIImageView 可有效地处理CIImage
3.2 Advanced Image Processing
- 使用CVPixelBuffer在Metal,Vision,Accelerate框架之间传递数据
- 不要重复已经做过的工作
- 不要将工作在CPU和GPU间来回传递
- 确保给Accelerate传递正确的buffer格式
4. 总结
- 采用预取和异步decode的方式
- 使用UIImageView 和 UILabel来减少Backing Store
- 不要无意中关闭了custom draw中系统尝试做的优化
- 推荐使用 Image Assets
- 不要过度依赖Preserve Vector DataP