面试中常被问到的问题之一,如何保持页面流畅。
研究一下CPU资源消耗的原因和解决方案
1.对象的创建
对象的创建会分配内存、调整属性、甚至还有读取文件的操作,比较消耗CPU资源。因此可以:
(1) 尽量用轻量的对象代替重量的对象,如CALayer比UIView轻量的多,在不需要响应触摸事件时,用CALayer显示更合适;
(2) 如果对象不涉及 UI 操作,尽量放到后台线程去创建;
(3) 通过 storyboard 创建视图对象时,其资源消耗会比直接通过代码创建对象要大非常多,所以尽量避免使用;
(4) 尽量推迟对象创建的时间,并把对象的创建分散到多个任务中去;
(5) 如果对象可以复用,并且复用的代价比释放、创建新对象要小,那么这类对象应当尽量放到一个缓存池里复用。
2.对象调整
对象的调整也是经常消耗CPU资源的地方。尤其是CALayer:
(1) CALayer 内部没有属性,当调用属性方法时,它内部是通过运行时 resolveInstanceMethod 为对象临时添加一个方法,并把对应属性值保存到内部的一个 Dictionary 中,同时还会告知 delegate、创建动画等等,非常消耗资源;
(2) UIView 关于显示相关的属性 (比如 frame/bouds/transform等)实际上都是CALayer 属性映射出来的,所以对UIView 的这些属性进行调整时,消耗的资源要远大于一般的属性,因此应该尽量减少类似的不必要的属性的修改;
(3) 当视图层次调整时,UIView、CALayer 之间会出现很多调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。
3.对象销毁
当容器类持有大量对象时,其销毁时的资源消耗就非常明显。所以,尽量去后台线程释放对象。可以这么做:把对象捕获到 block 中,然后扔到后台队列去随便发送个消息以避免编译警告,就可以让对象在后台线程销毁了
4.对象布局
在后台线程提前计算好试图布局、并对视图的布局进行缓存。
不论通过何种技术对视图进行布局,最终都会落到对 UIView.frame/bounds/center 等属性的调整上
5.Autolayout
这是苹果本身提倡的技术,在大部分情况下能很好的提升开发效率,但对于复杂视图来说常会产生严重的性能问题。随着视图数量的增长,Autolayout 带来的CPU消耗会呈指数级增长
6.文本计算
如果一个界面中包含大量的文本,文本的宽高计算会占用很大一部分资源,并且不可避免
7.文本渲染
屏幕上能看到的所有的文本内容控件包括 UIWebView,在底层都是通过 CoreText 排版、绘制为 Bitmap 显示的,并且该排版、绘制都是在主线程进行的。
显示大量文本时,CPU 的压力非常大,可以通过自定义文本控件,用TextKit 或最底层的 CoreText 对文本异步绘制,尽管麻烦但优势强大:
(1) CoreText 对象能直接获取文本的宽高等信息,避免了多次计算(调整 UILabel 大小时算一遍、UILabel 绘制时内部再算一遍);
(2) CoreText 对象占用内存较小,可以缓存下来以备稍后多次渲染。
8.图片解码
用 UIImage 或者 CGImageSource 的方法创建图片时,图片数据并不会立刻解码。图片设置到UIImageView 或者 CALayer.contents 中,并且CALayer 被提到 GPU前,CGImage 中的数据才会得到解码。
该步是发生在主线程,并且不可避免。如果想绕开这个机制,常见的方法是在后天线程先把图片绘制到 CGBitmapContext中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都自带这个功能。
9.图像的绘制
是指用那些以CG开头的方法把图像绘制到画布中,然后从画布创建图片并显示。常见的就是 [ UIView drawRect: ]。CoreGraphic 方法通常是线程安全的,所以图像的绘制可以放到后台线程运行。如下:(实际情况比这个复杂,但原理基本一致)
对于只需要圆角的某些场合,可以用一张已经绘制好的圆角图片覆盖到原视图上来模拟出相同的视觉效果
最彻底的做法:把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。