美文网首页
iOS性能优化

iOS性能优化

作者: 小心韩国人 | 来源:发表于2019-12-22 02:22 被阅读0次

要优化iOS App的性能,就得先搞清楚屏幕的成像原理.在屏幕的成像过程中,CPU 和 GPU起着至关重要的作用.

一:屏幕成像原理:

  • CPU (Central Processing Unit):中央处理器
    对象的创建和销毁,对象属性的调整,布局计算,文本的计算和排版,图片的格式转换和解码,图像的绘制 (Core Graphics).
  • GPU (Graphics Processing Unit)图形处理器
    纹理的渲染.

手机要把内容要想显示到屏幕上,是需要CPU和GPU相互协作才能完成的:
CPU主要负责计算,比如说界面的布局排版,文字的大小和颜色,图片显示的解码等操作都需要CPU计算,CPU将计算好的数据提交给GPU,GPU就将CPU计算好的数据进行渲染.因为屏幕显示的数据是特定格式的,只有通过GPU渲染过的数据才能显示在屏幕上;GPU把渲染过的数据放到帧缓存区,视频控制器从帧缓存区读取数据显示到屏幕上.


CPU 和 GPU 相互协作
  • iOS是双缓存机制,有前帧缓存和后帧缓存.渲染效率比较高.
  • 在显示一帧数据(一屏幕数据)之前,首先会发出垂直同步信号 (VSync),一旦发出垂直同步信号就意味着要显示一帧的数据了;接下来再发送水平同步信号 (HSync),发出水平同步信号就意味着一行一行的显示数据,直到填充整个屏幕为止.如果一帧的数据显示完毕,会再次发送垂直同步信号开始下一帧数据的显示,如果把每一帧的数据连起来看起来就和动画一样了.
    屏幕成像原理

二:卡顿产生的原因

有时候我们拖拽屏幕会发现屏幕不是很流畅,并且会有卡顿现象.那么产生卡顿的原因是什么呢?
CPU计算数据和GPU渲染数据都是需要消耗时间的,如果垂直同步信号发出的时候,CPU和GPU还没有把数据处理好,就会把CPU和GPU上一次处理好的数据显示在屏幕上.而CPU和GPU当前正在计算和渲染的数据就丢失了,俗称掉帧.也就是说屏幕上显示的还是上一帧( 上一页 )的数据,所以就造成了卡顿.而刚才CPU和GPU没有处理好的数据只有等到下一次垂直同步信号的到来才能显示.如图所示:

卡顿产生的原因
  • 知道了屏幕卡顿的原因,就有了解决卡顿的思路:尽量减少CPU , GPU资源消耗,缩短CPU 和 GPU 消耗的时间
  • 要保证屏幕不卡顿,就要保证1s刷新60帧,每隔16ms(毫秒)就会又一次VSync信号.也就是我们要在16毫秒内完成CPU的计算和GPU的渲染工作,这样就不会掉帧

三:卡顿优化

  1. 尽量使用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer代替UIView.
  2. 不要频繁的调整UIView的相关属性,比如:frame , bounds , transform等,尽量减少不必要的修改.因为每次调整都要重新计算渲染,消耗的性能就比较多.
  3. 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改.
  4. Autolayout会比直接设置frame消耗更多的CPU资源.
  5. 图片的size最好跟UIImageViewsize保持一致.如果图片过大CPU会进行伸缩处理,消耗资源.
  6. 控制线程的最大并发数.
  7. 尽量把耗时的操作放到子线程.
    文本处理(尺寸计算,绘制)
    图片处理(解码,绘制):通过UIImage imagename:方法加载的图片并不能直接显示在屏幕上,这样加载的仅仅是经过压缩的图片的二进制数据,如果想要把图片渲染到屏幕上还要对图片进行解码,解码成屏幕需要的格式.所以如果图片比较多比较大的话,可以提前放到子线程进行解码.最后再放到主线程显示,这样主线程就不会有这些解码的工作就不会造成卡顿.

四:卡顿检测

到现在我们知道,卡顿主要是因为在主线程执行了比较耗时的操作造成的.我们可以添加Observer到主线程的runloop中,通过监听runloop状态切换的耗时,以达到监控卡顿的目的.

五:耗电优化

耗电操作主要包含4个方面:

  1. CPU处理,Processing
  2. 网络,Networking
  3. 定位,Location
  4. 图像,Graphics

优化思路:

  1. 尽可能降低CPU,GPU功耗.
  2. 少用定时器.
  3. 优化I/O操作:
    1. 尽量不要频繁写入小数据,最好批量一次性写入.
    2. 读写大量重要数据时,考虑dispatch_io,其提供了基于GCD的异步操作文件I/O的API.用dispatch_io系统会优化磁盘访问.
    3. 数据比较大的,建议采用数据库(比如SQLite,CoreData)
  4. 网络优化:
    1. 减少,压缩网络数据.比如之前和服务器交互是采用XML,提交比较大.后来采用json,现在有的公司已经使用protobuffer / protocol buffer.
    2. 如果多次请求的结果是相同的,尽量使用缓存.NSCache
    3. 使用断点续传,否则网络不稳定时可能多次传输相同的内容.
    4. 网络不可用时不要尝试执行网络请求.
    5. 让用户可以取消长时间运行或者速度很慢的操作,设置合适的超时时间.
    6. 批量传输(减少发送网络请求的数量).比如:下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块的下载.如果下载广告,一次性多下载一些,然后慢慢展示.如果下载电子邮件,一次下载多封,不要一封一封的下载.
  5. 定位优化:
    1. 如果只是需要快速确定用户位置,最好用CLLocationManagerrequestLocation方法.定位完成后会自动让定位硬件断点.
    2. 如果不是导航引用,尽量不要实时更新位置,定位完毕就关掉定位服务.
    3. 尽量降低定位精度.比如尽量不要使用精度最高的KCLLocationAccuracyBest.
    4. 需要后台定位时,尽量设置pauseLocationUpdatesAutomaticallyYES.如果用户不太可能移动的时候,系统会自动暂停位置的更新.
    5. 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion.
  6. 硬件检测优化:
    用户移动,摇晃,倾斜设备时,会产生动作(motion)事件.这些事件由加速计,陀螺仪,磁力计等硬件检测.在不需要检测的场合,应该及时关闭这些硬件.

六:App启动优化

App的启动可以分为两种:

  1. 冷启动(Cold Launch):从零开始启动App.
  2. 热启动(Warm Launch):App已经在内存中,在后台存活着,再次点击图标启动App.
    而我们要做的启动优化,主要是对冷启动进行优化.一般来说,冷启动的时间只要在400毫秒以内就没有问题.超过400毫秒就需要优化了.
    通过添加环境变量可以打印出App的启动时间分析:
    Edit scheme -> Run -> Arguments -> Envirnvironment Variables下添加:
    1. DYLD_PRINT_STATISTICS设置为1:打印粗略信息.
    2. DYLD_PRINT_STATISTICS_DETAILS设置为1:打印详细信息
  3. App的冷启动可以概括为3大阶段:
    1. dyld:加载可执行文件,加载动态库阶段.
    2. runtime:初始化OC结构,比如初始化类,分类等等.
    3. main:调用main函数.

启动优化分析:

  1. dyld:
    我们点击App图标启动一个App,最开始是由dyld ( dynamic link editor )的程序去装载App的可执行文件.dyldApple的动态链接器,不管是iOS项目还是Mac OS 项目都会用到它.它是用来装载Mach-O文件的.(可执行文件,动态库等等).
    可执行文件包含我们所写的代码,并不包括项目中用到的动态库(比如:UIKit \ UIFondation),可执行文件中只有动态库的依赖信息,dyld出了要加载可执行文件到内存中以外,还要检查Mach-O文件依赖哪些动态库,然后去加载这些动态库.加载动态库的时候还会检查这个动态库是否还依赖其他动态库,如果依赖就递归查找加载到内存.
    dyld把可执行文件和动态库都加载到内存中后,就会通知runtime进行下一步处理.

  2. Runtime:
    启动App时,runtime所做的事情:

    1. 调用map_images进行可执行文件内容的解析和处理.
    2. load_images中调用call_load_methods,调用所有classcategory+load方法.
    3. 进行各种objc结构的初始化.(注册Objc类,初始化类对象等等).
    4. 调用C++动态初始化器 和 __attribute__((constructor))修饰的函数.
      到此为止,可执行文件和动态库中的所有符号(Class , Protocol , Selector , IMP , ...)都已经按格式成功加载到内存中,被runtime所管理.整个OC的运行环境就已经搭建好了.

总结:

  1. App的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库.
  2. 并有Runtime负责加载成objc定义的结构.
  3. 所有初始化工作结束后,dyld就会调用main函数.
  4. 接下来就是UIApplicationMain函数,AppDelegatedidFinishLaunchingWithOptions:方法.

启动优化思路:

  1. dyld:

    1. 减少动态库,合并动态库(定期清理不必要的动态库).
    2. 减少Objc类,分类的数量,减少Selector数量.(定期清理不必要的类,分类).
    3. 减少C++虚函数的数量.一旦有虚函数就会多维护一张虚表.
    4. Swift尽量使用struct,不要使用类.
  2. runtime:
    +initialize方法和dispatch_once取代所有的__attribute__((constructor)),C++ 动态构造器,Objc 的 +load方法.

  3. main:

    1. 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部放在didFinishLaunchingWithOptions方法中.
    2. 按需加载.

七:安装包瘦身

安装包主要由可执行文件,资源组成.

  1. 针对资源(图片,音频,视频 等):

    1. 采取无损压缩.
    2. 去除没有用到的资源.可以用github上的此工具.
  2. 可执行文件的瘦身:

    • 编译器优化:
      1. Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES
      2. 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions
      3. 利用AppCode检测未使用的代码:菜单栏 -> Code -> Inspect Code
  3. 生成LinkMap文件,可以查看可执行文件的具体组成


    image.png
  4. 可借助第三方工具解析LinkMap文件.

相关文章

网友评论

      本文标题:iOS性能优化

      本文链接:https://www.haomeiwen.com/subject/gqugnctx.html