iOS 如何优化 App

App 启动时间优化

优化的时候,我们将启动时间分为 pre-main 时间和 main 函数到第一个界面渲染完成时间这两个部分。

1. pre-main阶段

  • 1.1. 加载应用的可执行文件
  • 1.2. 加载动态链接库加载器dyld(dynamic loader)
  • 1.3. dyld递归加载应用所有依赖的dylib(dynamic library 动态链接库)

2. main()阶段

  • 2.1. dyld调用main()
  • 2.2. 调用UIApplicationMain()
  • 2.3. 调用applicationWillFinishLaunching
  • 2.4. 调用didFinishLaunchingWithOptions

1.pre-main介绍

   此阶段主要做的事情:

  • 加载APP的可执行文件
  • 加载动态链接库加载器的dyld
  • dyld递归加载所有依赖的动态链接库dylib,包括iOS系统的以及APP依赖的第三方库

dyld加载主要分四步:

加载dylib

分析每个dylib(大部分是iOS系统的),找到其Mach-O文件,打开并读取验证有效性,找到代码签名注册到内核,最后对dylib的每个segment调用mmap()。

优化思路是

  • 尽量减少dylib的使用个数,谨慎使用第三方SDK

rebase/bind

dylib加载完成之后,它们处于相互独立的状态,需要绑定起来。

优化思路是:

  • 减少OC类、selector、category的数量。
  • 减少C++虚函数的数量。
  • 使用Swift struct,减少符号的数量。

OC setup

OC的runtime需要维护一张类名与类的方法列表的全局表。
dyld做了如下操作:

  • 对所有声明过的OC类,将其注册到这个全局表中(class registration)
  • 将category的方法插入到类的方法列表中(category registration)
  • 检查每个selector的唯一性(selector uniquing)

initializer

这是pre-main阶段最耗时的部分。dyld运行APP的初始化函数,调用每个OC类的+load方法,调用C++的构造器函数(attribute((constructor))修饰),创建非基本类型的C++静态全局变量,然后执行main函数。

优化思路是

  • 尽量避免在+load方法里执行的操作,可以推迟到+initialize方法中。
  • 减少C++构造器函数个数。
  • 减少C++静态全局变量的个数。

其中 pre-main 苹果提供了内建的测量方法, Xcode 中 Edit scheme -> Run -> Auguments 将环境变量 DYLD_PRINT_STATISTICS 设为 1 

//结果为
Total pre-main time: 2.4 seconds (100.0%)
         dylib loading time: 433.13 milliseconds (17.8%)
        rebase/binding time: 1.8 seconds (77.8%)
            ObjC setup time:  71.79 milliseconds (2.9%)
           initializer time:  31.60 milliseconds (1.3%)
           slowest intializers :
             libSystem.B.dylib :   3.78 milliseconds (0.1%)
//解读
1、main()函数之前总共使用了2.4s

2、加载动态库用了433.13ms,指针重定位使用了1.8s,ObjC类初始化使用了71.79ms,各种初始化使用了31.60ms。

3、在初始化耗费的31.60ms中,用时最多的初始化是libSystem.B.dylib。
 

main()函数之前耗时的影响因素

  • 动态库加载越多,启动越慢。

  • ObjC类越多,启动越慢

  • C的constructor函数越多,启动越慢

  • C++静态对象越多,启动越慢

  • ObjC的+load越多,启动越慢

优化思路总结是

1. 移除不需要用到的动态库(如:未使用的第三方SDK)
2. 移除不需要用到的类(如:无用文件及资源删除,图片资源:去除无用的图片; 适当进行图片压缩)
3. 合并功能类似的类和扩展
4. 尽量避免在+load方法里执行的操作,可以推迟到+initialize方法中。

2.main()阶段

AppDelegate的didFinishLaunchingWithOptions

该方法中有很多初始化操作,如日志,统计,SDK配置等。
尽量做到只放必需的,其他的可以延迟到HomeViewController展示完成(viewDidAppear)以后。

微信分享SDK初始化

[WXApi registerApp:WXApi_APPKEY]

放到首页加载之后(进一步优化:在保存分享页真正需要的时候再去初始化也行,如对应ViewController的initialize中)

HomeVC的viewDidLoad到viewDidAppear之间

如果在AppDelegate构建ViewController(不管是采用storyboard还是代码构建),该ViewController的viewDidLoad会立即执行。

Home界面去掉storyboard,改为纯代码构建

因为storyboard需要先解码,虽然开发效率高,但涉及到对执行速度比较敏感的启动阶段,最好还是使用纯代码构建。并且,代码构建对于多人协作开发也是更加友好的。

启动创建的相关视图

尽量使用代码创建,避免使用xib,理由同storyboard。

viewDidAppear之前的耗时操作调整

对于一些不必要的初始化操作,可以调整到viewDidAppear执行时进行。
通过标记确保viewDidAppear中的一次性的初始化操作不要重复执行多次。
这里,把超出0.01s的都做了调整,如果可以,尽量放到viewDidAppear之后执行,主要是一些逻辑上的初始化、一些非必需界面的构建等操作。必要时,可以使用一些placeholder的方式来填充界面。

到viewDidAppear开始执行的时候,用户已经看到了APP的首屏,即宣告启动结束。

使用缓存或placeholder

对于一些需要在首页上展示数据或图片的APP,可以优先使用本地缓存数据或采用placeholder的方式,之后再将网络请求数据展示并更新本地缓存。

NSLog

每次使用NSLog都会隐式创建一个calendar,比较耗时,因此一般仅针对测试版本进行log打印。

NSUserDefaults

NSUserDefaults的操作对象是Library文件夹下的一个plist文件,若该文件很大,一次性读取到内存中会有较大耗时,可以考虑精简或拆分。但实际上的影响并不大。

优化思路总结

同时一般来说,优化应该在项目完成稳定之后进行,避免过早优化。


性价比最高的优化阶段就是main函数之后的一些逻辑整理,尽量将不需要的耗时操作延迟到首屏展示之后执行。

梳理各个三方库,找到可以延迟加载的库,做延迟加载处理,比如放到首页控制器的viewDidAppear方法里。

梳理业务逻辑,把可以延迟执行的逻辑,做延迟执行处理。比如检查新版本、注册推送通知等逻辑。

避免复杂/多余的计算。

避免在首页控制器的viewDidLoad和viewWillAppear做太多事情,这2个方法执行完,首页控制器才能显示,部分可以延迟创建的视图应做延迟创建/懒加载处理。

采用性能更好的API。

首页控制器用纯代码方式来构建。

参考:

  1. App Startup Time: Past, Present, and Future
  2. [iOS]一次立竿见影的启动时间优化
  3. iOS App 启动性能优化
  4. APP启动优化的一次实践
  5. 阿里数据iOS端启动速度优化的一些经验

猜你喜欢

转载自blog.csdn.net/LIN1986LIN/article/details/86062010