二进制重排(一)
对于app
启动来说分为两个阶段:
- 第一个阶段是在
main
函数之前,操作系统加载app可执行文件到内存,进行一系列的加载和链接工作,最后dyld
调起main
函数,这个过程叫pre-main
; - 第二个阶段就是
main
函数开始到appdelegate
的-didFinishLauching
方法到展示首页的内容为止。
启动时间检测
在 Xcode 13 & iOS 15 之前,Xcode 已经为我们提供了便捷的方法,在项目里点击Edit Scheme
:
选择Run
,环境变量填入:Value
值填:1
:
在设置完环境变量之后运行打印:
可以看到main
函数运行之前,pre-main
阶段运行所消耗的时间,这是用一个老项目跑在真机上,差不多是120
毫秒的消耗。
time 里面又分为4个阶段:
- dylib loading time
- rabase/binding time
- Objc setup time
- initializer time
可以看到有slowet intializers是显示上面显示上面4个阶段里耗时所占比例较大的库文件等
优化方法:
- 对于
dylib loading time
加载时间的优化:可以减少动态库的数量,这里的动态库是指自己制作的动态库,而非系统的动态库 ,像苹果的libSystem.B.dylib
或者Foundation
等动态库已经在共享缓存里,系统已经做了最大优化,我们无需处理,而对于我们自己制作的动态库,苹果建议不要超过6
个; - 对于
Objc setup time
加载时间的优化: 它做的事情是注册Objc
所有的类,相当于在可执行文件Mac-o
中读取所有的类,把他们放到一张表里面去,这些类就能被我们所使用,包括runtime
动态添加一个类,它都需要register
函数做注册操作。
因此,我们所有使用到的类在系统加载时候都有一个注册的过程,所以,不管我们写的类最终有没有使用到,它都会有注册操作,这最终会影响到 程序启动的时间。
如何检测项目中有哪些类没有被用到?
- 打开终端,运行下面的命令:
gem install fui
- 查看是否成功:
fui help
如何使用?
拿一个测试项目举例, 在项目点击到终端:
就可以找到项目中没有使用的类
- 这样就可以根据需要删除一些一直没有使用的类文件(删之前请做好备份,主要针对老项目或者那些很多人接手的);
- 还有一些方式可以查看哪些类是没被使用过,但是大部分类都是有用的,所以其实对于启动优化提升可能不是很明显,这就是
setup
阶段的优化; - 另外,
github
有一个项目LSUnusedResources-master
,可以清楚未使用的图片,减少包体积。
运行完会看到软件界面:
选择项目路径,点击search
会出现没有使用的图片资源
这对打包app
体积优化有帮助,尤其是老项目
在ObjC setup time这个阶段之后,还有一个initializer time,在这个阶段主要是各种初始化操作,比如初始化ObjC类的load函数,执行 C++的构造函数,load函数加载会在main执行之前,所以尽量不要在load里面做一些耗时操作,它都会影响app的启动时间。
符号绑定
再回过头来说一下rebase/binding time binding
就是符号绑定,这里拿一个小的可执行文件举例:
xcrun nm -nm main
得到:
最上面两个(undefined)
未定义里面的 binder
就是用来符号绑定的,在进行静态链接的时候,printf
函数会被标记来自libSystem
动态库, 但是只知道哪个库是没有用的,需要知道函数的切确地址,一旦执行了_printf
函数binder
就会把libStystem
里面函数真正实现的地址和_printf
这个符号进行绑定,调用_printf
能够真正的去执行函数的实现,这个过程就是binding
符号绑定。
虚拟内存
另一个rebase
,他是做指针的修复(翻译),这里涉及到虚拟内存的概念。