CPU 资源消耗原因和解决方案
- 对象创建
原因:对象的创建会分配内存、调整属性,甚至还有文件读写等操作,比较消耗 CPU 资源。那么使用轻量级的对象,可能会对性能有所优化。比如 CALayer 比 UIView 要轻量许多,那么不需要响应触摸事件的对象,用 CALayer 比较合适。如果创建的对象不涉及到 UI 界面刷新,那就尽量放到后台线程去创建。但是可惜的是包含有 CALayer 的控件,都只能在主线程进行创建和操作。通过 sb 创建视图对象时,其资源消耗要比直接使用代码创建对象要大的多。在性能敏感的界面中,sb 不是一个好的选择。
解决方案:
(1) 尽量推迟对象创建的时间,并把对象的创建分配到多个任务中去(推荐使用懒加载)。
(2) 如果对象可以复用,而且复用的代价比释放、创建新对象要小,那么这类对象要尽量放在一个缓存池中复用。
- 对象调整
原因:对象调整也是经常消耗 CPU 资源的地方,在这里特别说明一下 CALayer,CALayer 内部并没有属性,当调用属性方法的时候,它内部是通过运行时为对象临时添加一个方法,并把对应的值保存在内部的一个字典当中,同时还会通知 delegate、创建动画等,非常消耗资源。UIView 的关于显示的相关属性(frame/bounds/transform)等实际上都是 CALayer 的属性映射而来的,所以对 UIView 的这些属性进行调整的时候,消耗的资源要远大于一般的属性。
解决方案:当视图层次调整的时候,消耗的资源要远大于一般的属性,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。
- 对象销毁
原因:对象销毁虽然消耗资源不多,但累计起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗也就相当明显。
解决方案:对象的释放也尽量放到后台线程中去执行。这里有个坑:把对象捕捉到 block 中,然后扔到后台去随便发个消息以避免编译器警告,就可以让对象在后台销毁了。
- 布局计算
视图布局的计算是 App 中常见的消耗 CPU 资源的地方,如果能在后台线程中提前计算好视图布局,并对布局进行缓存的话,那么这个地方就不会出现任务性能问题了。
不论通过何种技术对视图进行布局,最终都会落到 UIView.frame/bounds/center 等属性的调整上,所以尽量提前计算调整好布局,在需要时一次性调整好对应属性,而不要多次、频繁的计算和调整这些属性。
- Autolayout
Autolayout 是苹果集成的技术,在大部分情况下也可以很好的提高开发效率,但是 Autolayout 对复杂视图来说常常会产生严重的性能问题。随着视图的增长, Autolayout 带来的 CPU 消耗会呈指数级上升。如果不想手动调整 frame 等属性,可以使用第三方 SDK。
- 文本计算
如果一个界面中包含大量的文本,文本的宽高计算会占用很大一部分资源,并且不可避免。如果没有特殊要求,可以参考下 UILabel 内部的实现方式:[NSAttributeString boundingRectWithSize:context:]来计算文本宽高,用 [NSAttributrString drawWithRect:options:context:] 来绘制文本。尽管这两个方法性能不错,但是扔需要放到后台线程以避免阻塞主线程。
如果你使用 CoreText 绘制文本,那就可以先生成 CoreText 排版对象,然后自己计算,并且 CoreText 对象还可以保留以供稍后绘制使用。
- 文本渲染
屏幕上能看到的所有文本内容控件,包括 UIWebView,在底层都是通过 CoreText 排版,绘制为 bitmap 图显示的。常见的文本控件(UILabel、UITextView等),其排版和绘制都是在主线程中进行的。当显示大量文本的时候,CPU 的压力会非常大。对此解决方案只有一个,那就是自定义文本控件,用 TextKit 或者最底层的 CoreText 对文本异步绘制。尽管这实现起来非常麻烦,但带来的优势也非常大,CoreText 对象创建好以后,能直接获取文本宽高等信息,避免了多次计算(调整 UILabel 时计算一遍,UILabel 绘制时内部在计算一遍);CoreText 占用内存较少,可以缓存下来以备多次渲染。
- 图片的解码
当你用 UIImage 或者 CGImageSource 的那几个方法去创建图片的时候,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 CPU 前,CGImage 中的数据才会得到解码,这一步是发生在主线程的,并且不可避免。如果想绕开这个机制,常见的做法就是在后台线程先把图片绘制到 CGBitmapContent 中,然后从 Bitmap 直接创建图片。目前常见的网络图片库都有这个功能
- 图片的绘制
图片的绘制通常是指那些以 CG 开头的方法把图像放到画布中,然后从画布中创建图片并显示的一个过程,最常见的地方就是 [UIView drawRect:] 里面了。由于 CoreGraphic 方法通常都是线程安全的,所以图像的绘制可以很容易的放到后台线程去执行,一个简单的异步绘制的过程大概如下(实际情况会复杂的多,但过程原理大致一样):
- (void) display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}