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 = YESlayer.cornerRadius > 0;可以用CoreGraphics 绘制裁剪圆角。
  • 阴影(如果设置了layer.shadowPath)不会产生离屏渲染。
  • 光栅化,layer.shouldRasterize = YES
    • 光栅化的好处:在其他属性触发离屏渲染的同时,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用。shouldRasterize = YES,这将隐式的创建一个位图,各种阴影遮罩等效果也会保存到位图中并缓存起来,从而减少渲染的频度(不是矢量图)。
      • 举个例子:如果在滚动tableView时,每次都执行圆角设置,肯定会阻塞UI,设置这个将会使滑动更加流畅。当shouldRasterize设成true时,layer被渲染成一个bitmap,并缓存起来,等下次使用时不会再重新渲染了。实现圆角本身就是在做颜色混合(blending),如果每次页面出来时都blending,消耗太大,这时shouldRasterize = yes,下次就只是简单的从渲染引擎的cache里读取那张bitmap,节约系统资源。

但光栅化又会导致离屏渲染,影响图像性能,那么光栅化是否有助于优化性能,就取决于光栅化创建的位图缓存是否被有效复用,而减少渲染的频度,可以视同Instruments进行检测:
当你使用光栅化时,你可以开启“Color Hits Green and Misses Red”,来检查该场景下光栅化是否是一个好的选择。
如果光栅化的图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。
注意:
对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费。

卡顿检测

这里的检测主要是针对主线程执行的耗时操作,可以通过RunLoop来检测:添加Observer到主线程的RunLoop中,通过监听RunLoop状态的切换的耗时,达到监控卡顿的目的。

2、耗电优化

耗电的主要方面:

  • CPU处理
  • 网络请求
  • 定位
  • 图像渲染

优化思路

  • 尽可能的降低CPU、GPU功耗。
  • 少用定时器
  • 优化I/O操作
    • 尽量不要频繁的写入小数据,最好一次性批量的写入
    • 读写大量重要数据的时候,可以用dispatch_io,它提供了基于GCD的异步操作文件的API,使用该API会优化磁盘访问。
    • 数据量大的时候,用数据库管理数据
  • 网络优化
    • 减少、压缩网络数据(JSON比XML文件性能更高)。
    • 若多次的网络请求结果相同,尽量使用缓存
    • 使用断点续传,否则网络不稳定的情况下可能多次传输相同的内容。
    • 网络不可用时,不进行网络请求。
    • 让用户可以取消长时间运行或者速度比较慢的网络操作,设置合适的超时时间。
    • 批量传输,如下载视频,不要传输很小的数据包,直接下载整个文件或者大块下载,然后慢慢展示。
  • 定位优化
    • 如果外面只是需要快速的确定用户的位置,用CLLocationManagerrequestLocation方法定位,定位完成后,定位硬件会自动断电。
    • 若不是导航应用,尽量不要实时更新位置,并为完毕就关掉定位服务。
    • 尽量降低定位精度,如不要使用精度最高的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文件(可执行文件和动态库等)。

图片.png

启动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

打开可看见各种信息:
图片.png

我们可根据这个信息针对某个类进行优化。

你可能感兴趣的:(iOS 性能优化)