iOS启动耗时分析

        启动耗时分析,一般我们会以main函数作为分割点,main之前和main之后main之前称为per-main 阶段。这个由dyld给你反馈应用的耗时。main之后由开发者自己检测。我们可以从main开始打点,到第一个页面显示为止。

通过实际的调试,我们得到各个函数的调用顺序如下:

启动页

main()

UIApplicationMain()

willFinishLaunchingWithOptions()

didFinishLaunchingWithOptions()

loadView()

viewDidLoad()

applicationDidBecomeActive()

启动页是在main()函数调用之前出来的,main()是程序的入口,里面调用了UIApplicationMain()。当App从didFinishLaunchingWithOptions()返回的时候,实际的UI立刻开始加载,但是在applicationDidBecomeActive()这个回调完成之前,UI即使已经初始化,但仍旧被阻塞着。

总的启动时间T包括main()调用之前的pre-main timeT0,

加上从main()到applicationDidBecomeActive()的时间T1。

pre-main阶段耗时

 main函数之前的检测苹果提供了支持,具体配置方式如图

----首先进入Edit Scheme

----然后配置的 key 为:DYLD_PRINT_STATISTICS

----然后我们再运行项目,该项目 pre-main 的耗时就会在控制台输出。

各阶段耗时分析

dylib loading time 动态库载入耗时

载入动态库,这个过程中,会去装载app使用的动态库,而动态库之间有它自己的依赖关系,所以会消耗时间去查找和读取。

优化建议: 

1.系统的动态库,做了优化。所以从效率的角度来说,尽可能使用系统库;

2.而对于开发者定义导入的动态库(dynamically linked shared library),则需要在花费更多的时间。Apple官方建议尽量少的使用自定义的动态库,或者考虑合并多个动态库,其中一个建议是当大于6个的时候,则需要考虑合并它们;

3.在性能上出发将动态库编译成静态库也会优化这部分时间;

rebase/binding time 修正符号和绑定符号耗时

Rebase:在镜像(MachO文件)内部调整指针的指向,针对mach-o在加载到内存中不是固定的首地址(ASLR)这一现象做数据修正的过程。

iOS4.3后引入了 ASLR ,MachO会被加载到随机地址,这个随机的地址跟代码和数据指向的旧地址会有偏差。dyld 需要修正这个偏差,做法就是将 dylib 内部的指针地址都加上这个偏移量。

binding:将指针指向镜像(MachO文件)外部的内容,binding就是将这个二进制调用的外部符号进行绑定的过程。

优化建议:

1.核心思想是在进行动态库的重定位和绑定(Rebase/binding)过程中减少指针修正;

2.减少Objective-C类数量,减少分类,减少实例变量和函数(删除不用的类以及冗余代码,再深一点就是减少第三方工具的使用,可以查看源码,自己实现);

3.减少C++虚函数;

4.多使用Swift结构体(推荐使用swift)

ObjC setup time OC类注册的耗时

主要做以下几件事来完成Objc Setup:

1、读取二进制文件的 DATA 段内容,找到与 objc 相关的信息

2、注册 Objc 类,ObjC Runtime 需要维护一张映射类名与类的全局表。当加载一个 MachO 时,它定义的所有的类都需要被注册到这个全局表中;

3、读取 protocol 以及 category 的信息,把category的定义插入方法列表 (category registration),

优化建议:

1.不刻意的去减少几个类,但是可以避免浪费;

2 随着项目的不断迭代,很多模块和方法已经被废弃但是却一直留存在项目中,导致项目越来越臃肿;

3.我们可以使用一些工具来查找项目中没有被用到的文件。从而达到优化;

initializer time 其他初始化,如上图,细分为其他的几个部分

1、Objc的+load()函数

2、C++的构造函数属性函数 形如attribute((constructor)) void DoSomeInitializationWork()

优化建议: 

1.我们能做的就是将不必须在+load方法中做的事情延迟到+initialize中;

2.这是因为+load方法是在app启动的时候就被调用,而+initialize方法则是在Class第一次使用的时候才调用,相当于是懒加载了。可以把+load中的代码移到initialize中,并结合dispatch_once来防止重复调用;

3.但是我们项目中只有在使用method swizzling的时候会在+load中调用方法。所以这一点也没什么好优化的;

pre-main阶段耗时总结:

1. 动态库加载越多,启动越慢

2. ObjC类,方法越多,启动越慢

3. ObjC的+load越多,启动越慢

4. C的constructor函数越多,启动越慢

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

main()到applicationDidBecomeActive()的阶段耗时

我们可以使用Xcode自带工具Instruments里面的Time Profiler来获取,也可以在main()的第一句和applicationDidBecomeActive()的最后一句加上获取时间的代码CFAbsoluteTimeGetCurrent(),

Time Profiler

工具通过Xcode工具栏中Product->Profile(command+i)可以启动,(也可以通过Xcode->Open Developer Tool->Instruments)启动后界面如下:

选择Time Profiler,打开后如图:

点击左上角红色按钮运行,勾选左下角Call Tree中Separate Thread和Hide System Libraries,等到第一个页面显示出来的之后,点击左上角暂停按钮,下面就会统计出每个步骤的耗时情况。这个时候我们就可以很容易得到启动时间T1。

针对这块时间的耗时优化总结:

我们通过Time Profiler拿到每个步骤的耗时之后,右下角的 Heaviest Trace 可查看比较消耗CPU的代码,双击点击进去可查看到对应的代码,进行修改。有些操作可以延后执行,或者异步执行等,这些需要根据自己的业务逻辑在处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转载自腾讯云 App启动过程 解析Info.plist加载相关信息,例如如闪屏沙箱建立、权限检查 Mach-O加载...
    乂滥好人阅读 1,018评论 0 0
  • 多作者合集,非商业行为,为自己学习巩固。特此声明 启动APP的时候就会花费较长的时间,用户体验很不好。所以针对AP...
    红色海_阅读 750评论 0 2
  • 一款 App 的启动速度,不单单是用户体验的事情,往往还决定了它能否获取更多的用户。这就好像陌生人第一次碰面,第一...
    vicentwyh阅读 1,428评论 0 10
  • 黑色的海岛上悬着一轮又大又圆的明月,毫不嫌弃地把温柔的月色照在这寸草不生的小岛上。一个少年白衣白发,悠闲自如地倚坐...
    小水Vivian阅读 3,133评论 1 5
  • 渐变的面目拼图要我怎么拼? 我是疲乏了还是投降了? 不是不允许自己坠落, 我没有滴水不进的保护膜。 就是害怕变得面...
    闷热当乘凉阅读 4,317评论 0 13