美文网首页
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