一般情况下,App 的启动分为冷启动和热启动:
- 冷启动: 系统中没有app进程,系统启动新进程。
- 热启动: 系统中已经存在对应app的进程。
可优化的阶段:
main前 -> main后 -> didFinishLaunchingWithOptions -> viewDidAppear
App在启动阶段都做了什么? main()函数之前:
- 系统加载Mach-o文件
- 系统加载dyld程序
- dyld程序分析Mach-o中Load Commands中的内容分析依赖的动态库,递归的加载动态库:动态链接库包括:iOS 中用到的所有系统 framework,加载OC runtime方法的libobjc,系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。
- rebase/bind。由于(ASLR)存在,可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不固定所以需要两步修复,rebase修复的是指向当前镜像内部的资源指针; 而bind指向的是镜像外部的资源指针。
- Objc setup 注册Objc类 (class registration) 把category的定义插入方法列表 保证每一个selector唯一
- initializers 调用+load()(直接通过函数地址调用,不通过消息转发) C++的构造函数属性函数 形如attribute((constructor)) void DoSomeInitializationWork() C++的构造函数属性函数 形如attribute((constructor)) void DoSomeInitializationWork()
- call main进入程序app main()方法
针对3.
函数能优化的:
- 减少非系统库使用
- 合并非系统库
- 直接使用代码
针对4.
函数能优化的:
- 减少Objc类数量, 减少selector数量
- 减少C++虚函数数
- 转而使用swift stuct(其实本质上就是为了减少符号的数量)
针对5.
- 减少 Objective-C Class、Selector、Category 的数量,可以合并或者删减一些OC类. 可以使用AppCode代码分析工具
针对6.
函数能优化的:
- 少在类的+load方法里做事情,尽量把这些事情推迟到+initiailize
- 减少attribute((constructor))
- dyld 开始将程序二进制文件初始化
- 交由 ImageLoader 读取 image,其中包含了我们的类、方法等各种符号
- 由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理
- runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class 的 +load 方法和其 Category 的 +load 方法
至此,可执行文件中和动态库所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被 runtime 所管理,再这之后,runtime 的那些方法(动态添加 Class、swizzle 等等才能生效)。整个事件由 dyld 主导,完成运行环境的初始化后,配合 ImageLoader 将二进制文件按格式加载到内存,动态链接依赖库,并由 runtime 负责加载成 objc 定义的结构,所有初始化工作结束后,dyld 调用真正的 main 函数。如果程序刚刚被运行过,那么程序的代码会被dyld缓存,因此即使杀掉进程再次重启加载时间也会相对快一点,如果长时间没有启动或者当前dyld的缓存已经被其他应用占据,那么这次启动所花费的时间就要长一点
测量工具及如何测量
- Edit Scheme -> DYLD_PRINT_STATISTICS = true DYLD_PRINT_STATISTICS_DETAILS = true
Total pre-main time: 763.22 milliseconds (100.0%)
dylib loading time: 121.27 milliseconds (15.8%)
rebase/binding time: 43.05 milliseconds (5.6%)
ObjC setup time: 43.51 milliseconds (5.7%)
initializer time: 555.39 milliseconds (72.7%)
slowest intializers :
libSystem.B.dylib : 5.39 milliseconds (0.7%)
libMainThreadChecker.dylib : 50.21 milliseconds (6.5%)
libglInterpose.dylib : 359.87 milliseconds (47.1%)
AFNetworking : 34.86 milliseconds (4.5%)
TrafficRecords : 163.48 milliseconds (21.4%)
total time: 2.2 seconds (100.0%)
total images loaded: 520 (505 from dyld shared cache)
total segments mapped: 53, into 2284 pages
total images loading time: 1.0 seconds (48.4%)
total load time in ObjC: 43.51 milliseconds (1.9%)
total debugger pause time: 945.24 milliseconds (42.9%)
total dtrace DOF registration time: 0.00 milliseconds (0.0%)
total rebase fixups: 418,403
total rebase fixups time: 39.09 milliseconds (1.7%)
total binding fixups: 745,042
total binding fixups time: 491.61 milliseconds (22.3%)
total weak binding fixups time: 4.45 milliseconds (0.2%)
total redo shared cached bindings time: 492.10 milliseconds (22.3%)
total bindings lazily fixed up: 0 of 0
total time in initializers and ObjC +load: 555.39 milliseconds (25.2%)
libSystem.B.dylib : 5.39 milliseconds (0.2%)
libBacktraceRecording.dylib : 9.88 milliseconds (0.4%)
libobjc.A.dylib : 2.49 milliseconds (0.1%)
libMainThreadChecker.dylib : 50.21 milliseconds (2.2%)
libglInterpose.dylib : 359.87 milliseconds (16.3%)
libMTLCapture.dylib : 4.65 milliseconds (0.2%)
AFNetworking : 34.86 milliseconds (1.5%)
TrafficRecords : 163.48 milliseconds (7.4%)
total symbol trie searches: 1682230
total symbol table binary searches: 0
total images defining weak symbols: 56
total images using weak symbols: 132
优化前,images loading 和 initializer阶段比较耗时,这部分我们可以减少库的引用,和减少OC类,减少__attribute__((constructor))及优化+load方法
方法耗时测量
A4LoadMeasure:直接通过
getsectiondata
函数,读取编译时期写入mach-o
文件DATA
段的__objc_nlclslist
和__objc_nlcatlist
节,这两节分别用来保存no lazy class
列表和no lazy category
列表,所谓的no lazy
结构,就是定义了+load
方法的类或分类 A0PreMainTimer:详解juejin.cn/post/684490… Tips: Embedded Binaries 一栏表示把列表中的二进制文件,集成到最终生成的 .app 文件中 Linked Frameworks And Libraries 一栏表示链接时,按顺序依次链接列表中的库文件 所以如果是我们自己添加的库文件,需要将库文件添加进上面的两个列表中,否则要么 dyld 加载库镜像时出现 Library not loaded 错误,要么直接不链接这个库文件。而系统库则不需要设置 Embedded 栏 ,只需要设置 Linked 栏,因为实际设备中会预置这些库。 直接将A0PreMainTimer拖进项目中比较好,并确保A0PreMainTimer在所有其他库的前面,应为初始化函数的调用顺序将和 LC_LOAD_DYLIB 的排列顺序一致。 A0PreMainTimer大致流程:
- 首先获取所有的动态库,要过滤系统动态库。
- 然后通过
__objc_nlclslist
和__objc_nlcatlist
获取所有的类和分类,并且通过运行时hook住每个类和分类的load方法。 +load优化前
Load Measure Initializer Time: 6.448984 milliseconds
Total load time: 31.923175 milliseconds
_AFURLSessionTaskSwizzling load time: 13.692021 milliseconds
NSObject(Property) load time: 2.251029 milliseconds
NSMutableArray(Swizzling) load time: 1.685023 milliseconds
UMConfigure load time: 1.518011 milliseconds
NSObject(MJKeyValue) load time: 1.481056 milliseconds
UITableView(FDIndexPathHeightCacheInvalidation) load time: 1.322031 milliseconds
iConsole load time: 1.151085 milliseconds //可删除
NSValueTransformer(DPXMTLPredefinedTransformerAdditions) load time: 1.047015 milliseconds
TTTAttributedLabel load time: 1.004934 milliseconds
UMSocialSinaHandler load time: 0.949979 milliseconds
BLYWCSessionDelegateInterceptor load time: 0.926971 milliseconds
WXOMTAPluginNotify load time: 0.867009 milliseconds
UMSocialQQHandler load time: 0.784993 milliseconds
AMapBackTrace load time: 0.773072 milliseconds
AMapSystemInfo load time: 0.769973 milliseconds
UITableView(MJRefresh) load time: 0.620008 milliseconds
UIViewController(BuglyMartian) load time: 0.503063 milliseconds //可删除
UINavigationController(FDFullscreenPopGesture) load time: 0.135064 milliseconds
MobClick load time: 0.087023 milliseconds
Fabric load time: 0.084043 milliseconds //可删除
WXOMTADataConfigHolder load time: 0.081062 milliseconds
UMSocialManager load time: 0.071049 milliseconds
UIViewController(FDFullscreenPopGesturePrivate) load time: 0.060916 milliseconds
UMSocialWechatHandler load time: 0.026941 milliseconds
NSObject(BUGLY_WCSessionRuntime) load time: 0.016928 milliseconds //可删除
WXOMTAEnvHelper load time: 0.011921 milliseconds
UMSocialHandler load time: 0.000954 milliseconds
NSObject(AHClass) load time: 0.000000 milliseconds
NSObject(AHKeyValue) load time: 0.000000 milliseconds
UICollectionView(MJRefresh) load time: 0.000000 milliseconds
Load Measure Initializer Time: 5.154967 milliseconds
Total load time: 12.525916 milliseconds
_AFURLSessionTaskSwizzling load time: 10.613918 milliseconds
UITableView(FDIndexPathHeightCacheInvalidation) load time: 0.684977 milliseconds
UMConfigure load time: 0.355005 milliseconds
NSMutableArray(Swizzling) load time: 0.253916 milliseconds
UIViewController(FDFullscreenPopGesturePrivate) load time: 0.074029 milliseconds
WXOMTAEnvHelper load time: 0.071049 milliseconds
NSObject(MJKeyValue) load time: 0.068069 milliseconds
NSValueTransformer(DPXMTLPredefinedTransformerAdditions) load time: 0.056982 milliseconds
TTTAttributedLabel load time: 0.056028 milliseconds
AMapBackTrace load time: 0.054955 milliseconds
NSObject(Property) load time: 0.048995 milliseconds
UMSocialManager load time: 0.036001 milliseconds
UINavigationController(FDFullscreenPopGesture) load time: 0.034928 milliseconds
UITableView(MJRefresh) load time: 0.027061 milliseconds
AMapSystemInfo load time: 0.020981 milliseconds
MobClick load time: 0.018954 milliseconds
WXOMTADataConfigHolder load time: 0.017047 milliseconds
UMSocialQQHandler load time: 0.007987 milliseconds
UMSocialWechatHandler load time: 0.007987 milliseconds
UMSocialSinaHandler load time: 0.007987 milliseconds
WXOMTAPluginNotify load time: 0.007987 milliseconds
UMSocialHandler load time: 0.001073 milliseconds
NSObject(AHKeyValue) load time: 0.000000 milliseconds
NSObject(AHClass) load time: 0.000000 milliseconds
UICollectionView(MJRefresh) load time: 0.000000 milliseconds
项目优化步骤:
- 删除+load方法无用的库及内容。
- 测量耗时方法,并改造方法测量方法耗时
- 通过AppCode检测无用的代码
- 二进制重排