iOS 性能优化
iOS的性能优化主要可提现在以前的几个方面:卡顿优化、耗电优化、启动优化、安装包的瘦身。
1、卡顿优化
在了解卡顿优化相关之前我们需要了解屏幕成像原理和卡顿的成因。
屏幕成像原理
我们所看到的动态的屏幕的成像其实和视频一样,都是一帧一帧造成的。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(Horizonal Synchronization),简称HSync;当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(Vertical Synchronization),简称VSync。显示器通常以一个固定的频率进行刷新,这个刷新的频率就是VSync信号产生的频率。
卡顿成因
完成显示的过程:CPU计算数据->GPU进行渲染->屏幕发出VSync信号->成像,假如屏幕已经发出了VSync,但是GPU还没有渲染完成,则只能将上一次的数据显示出来,以至于当前计算的帧数据丢失,这样就产生了卡顿,当前的帧数据计算好后只能等下一周期去渲染了。
CPU(Central Processing Unit,中央处理器)
对象的创建、摧毁、属性的调整、布局的计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)都是通过CPU来做的。
GPU(Graphics Processing Unit,图形处理器)
图片.png所要显示的信息一般是通过CPU计算或者解码,经过CPU的数据交给GPU渲染,渲染的工作在帧缓存的地方完成,然后从帧缓存读取数据到视屏控制器上,最终显示在屏幕上。
在iOS中有双缓存机制,前帧缓存、后帧缓存,这样的渲染效率更高。
卡顿解决办法
主要思路就是:尽可能的减少CPU和GPU资源的消耗。
按照60fps的刷帧率,每隔16ms就会产生一个VSync信号,那么针对CPU和GPU有以下优化方案:
CPU
- 尽量用轻量级的对象如:不用处理事件的UI控件考虑使用CALayer。
-
不要频繁的调用
UIView
的相关属性如:frame、bounds、transform 等。 - 尽量提前计算好布局,在有需要的时候一次性的调整对应的属性,不要多次修改。
Autolayout
会比直接设置frame消耗更多的CPU资源。- 图片的size和UIImageView的size保持一致。
- 控制线程的最大并发数量。
- 耗时操作放入子线程;如文本的尺寸计算、绘制、图片的解码、绘制等。
GPU
- 尽量避免短时间内大量的显示图片。
- GPU能处理的最大纹理尺寸是4096 * 4096,超过这个尺寸就会占用CPU资源,所以纹理不能超过这个尺寸。
- 尽量减少透明图的数量和层次。
- 减少透明的视图(alpha < 1),不透明的就设置opaque为YES。
- 尽量避免离屏渲染。
离屏渲染
在OpenGL中,GPU有两种渲染方式:
On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作。
Off-Screen Rendering:在当前屏幕缓冲区外开辟新的缓冲区进行渲染操作。
离屏渲染操作消耗性能的原因:
在整个离屏渲染的操作过程当中,需要多次切换上下文环境,先是从当前屏幕切换到离屏,渲染结束后,将离屏缓冲区的渲染结果显示到屏幕上,上下文环境从离屏切换到当前屏幕,这个过程会造成性能的消耗。
一下操作会触发离屏渲染
- 遮罩,
layer.mask
- 圆角,同时设置
layer.masksToBounds = YES
,layer.cornerRadius > 0
;可以用CoreGraphics 绘制裁剪圆角。 - 阴影(如果设置了
layer.shadowPath
)不会产生离屏渲染。 - 光栅化,
layer.shouldRasterize = YES
-
光栅化的好处:在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。shouldRasterize = YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。
- 举个例子:如果在滚动tableView时,每次都执行圆角设置,肯定会阻塞UI,设置这个将会使滑动更加流畅。当shouldRasterize设成true时,layer被渲染成一个bitmap,并缓存起来,等下次使用时不会再重新渲染了。实现圆角本身就是在做颜色混合(blending),如果每次页面出来时都blending,消耗太大,这时shouldRasterize = yes,下次就只是简单的从渲染引擎的cache里读取那张bitmap,节约系统资源。
-
光栅化的好处:在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。shouldRasterize = YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。
但光栅化又会导致离屏渲染,影响图像性能,那么光栅化是否有助于优化性能,就取决于光栅化创建的位图缓存是否被有效复用,而减少渲染的频度,可以视同Instruments进行检测:
当你使用光栅化时,你可以开启“Color Hits Green and Misses Red”,来检查该场景下光栅化是否是一个好的选择。
如果光栅化的图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。
注意:
对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费。
卡顿检测
这里的检测主要是针对主线程执行的耗时操作,可以通过RunLoop来检测:添加Observer到主线程的RunLoop中,通过监听RunLoop状态的切换的耗时,达到监控卡顿的目的。
2、耗电优化
耗电的主要方面:
- CPU处理
- 网络请求
- 定位
- 图像渲染
优化思路
- 尽可能的降低CPU、GPU功耗。
- 少用定时器
- 优化I/O操作
- 尽量不要频繁的写入小数据,最好一次性批量的写入
- 读写大量重要数据的时候,可以用
dispatch_io
,它提供了基于GCD的异步操作文件的API,使用该API会优化磁盘访问。 - 数据量大的时候,用数据库管理数据
- 网络优化
- 减少、压缩网络数据(JSON比XML文件性能更高)。
- 若多次的网络请求结果相同,尽量使用缓存
- 使用断点续传,否则网络不稳定的情况下可能多次传输相同的内容。
- 网络不可用时,不进行网络请求。
- 让用户可以取消长时间运行或者速度比较慢的网络操作,设置合适的超时时间。
- 批量传输,如下载视频,不要传输很小的数据包,直接下载整个文件或者大块下载,然后慢慢展示。
- 定位优化
- 如果外面只是需要快速的确定用户的位置,用
CLLocationManager
的requestLocation
方法定位,定位完成后,定位硬件会自动断电。 - 若不是导航应用,尽量不要实时更新位置,并为完毕就关掉定位服务。
- 尽量降低定位精度,如不要使用精度最高的
KCLLocationAccuracyBest
。
-需要后台定位,尽量设置pausesLocationUpdatesAutomatically
为YES,若用户不怎么移动的时候,系统会自动暂停位置更新。
- 如果外面只是需要快速的确定用户的位置,用
启动优化
App的启动分为两种:冷启动、热启动
- 冷启动:表示从零启动App。
-
热启动:表示App已经存在内存中,在后台依然活动着,再次点击图标启动App。
App的启动优化主要针对的是冷启动的优化。通过添加环境变量可以打印出App的启动时间分析:Edit Scheme -> Run -> Arguments -> Environment Variables添加DYLD_PRINT_STATISTICS
设置为1。
图片.png
运行程序则会打印:
图片.png
这里打印的是在执行main
函数之前的耗时信息,若想打印更详细则添加环境变量为:DYLD_PRINT_STATISTICS_DETAILS
设置为1。
图片.png
App冷启动
冷启动可以分为三个阶段:dyld阶段、Runtime阶段、main阶段。
第一阶段就是处理程序的镜像阶段,第二个阶段是加载本程序的类、分类信息等等的Runtime阶段,最后是调用main函数阶段。
dyld
dyld(Dynamic Link Editor),Apple的动态链接器,可以用来装载Mach-O文件(可执行文件和动态库等)。
启动App时,dyld会装载App的可执行文件,同时会递归加载所有依赖的动态库,当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行做下一步的处理。
Runtime
启动App时,调用map_images
进行可执行文件的内容解析和处理,再load_images
中调用call_load_methods
调用所有Class和Category的load
方法,然后进行objc结构初始化(注册类、初始化类对象等)。然后调用C++静态初始化器和__attribute_((constructor))
修饰的函数,到此为止,可执行文件的和动态库中所有的符号(类、协议、方法等)都已经按照格式加载到内存中,被Runtime管理。
main
在Runtime阶段完成后,dyld会调用main函数,接下来是UIApplication函数,AppDelegate的application: didFinishLaunchingWithOptions:
函数。
启动优化思路
dyld
- 减少动态库、合并动态库,定期清理不必要的动态库。
- 减少类、分类的数量,减少Selector的数量,定期清理不必要的类、分类。
- 减少C++虚函数的数量
- Swift开发尽量使用struct。
虚函数和java中抽象的函数有点类似,但区别是,基类定义的虚函数,子类可以实现也可以不实现,而抽象函数子类一定要实现。
Runtime
- 用
inilalize
方法和dispatch_once
取代所有的__attribute_((constructor))
、C++静态构造器、以及Objective-C中的load
方法。
main
- 将一些耗时操作延迟执行,不要全部都放在
finishLaunching
方法中。
3、安装包瘦身
安装包(ipa)主要由可执行文件和资源文件组成,若不能妥善的管理则会造成安装包体积越来越大,所以针对资源优化我们可以将资源采取无损压缩,去除没用的资源。
- 对于可执行文件的瘦身,我们可以:
- 从编译器层面优化
- Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为 YES;
- 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO,Other C Flags添加
-fno-exceptions
; - 利用AppCode检测未使用代码:菜单栏->Code->Inspect Code;
- 编写LLVM插件检测重复代码、未调用代码;
- 通过LinkMap文件检测;
- 从编译器层面优化
LinkMap
Build Setting -> LD_MAP_FILE_PATH: 设置文件路径 ,Build Setting -> LD_GENERSTE_MAP_FILE -> YES
运行程序可看到:
图片.png
打开可看见各种信息:
图片.png
我们可根据这个信息针对某个类进行优化。