二进制重排(一)

136 阅读4分钟

二进制重排(一)

对于app启动来说分为两个阶段:

  • 第一个阶段是在main函数之前,操作系统加载app可执行文件到内存,进行一系列的加载和链接工作,最后dyld调起main函数,这个过程叫pre-main
  • 第二个阶段就是main函数开始到appdelegate-didFinishLauching方法到展示首页的内容为止。

启动时间检测

在 Xcode 13 & iOS 15 之前,Xcode 已经为我们提供了便捷的方法,在项目里点击Edit Scheme

Pasted Graphic.png

选择Run,环境变量填入:Value值填:1

Pasted Graphic 1.png

在设置完环境变量之后运行打印:

Pasted Graphic 5.png

可以看到main函数运行之前,pre-main阶段运行所消耗的时间,这是用一个老项目跑在真机上,差不多是120毫秒的消耗。

time 里面又分为4个阶段:

  • dylib loading time
  • rabase/binding time
  • Objc setup time
  • initializer time

可以看到有slowet intializers是显示上面显示上面4个阶段里耗时所占比例较大的库文件等

Pasted Graphic 6.png

优化方法:

  • 对于dylib loading time 加载时间的优化:可以减少动态库的数量,这里的动态库是指自己制作的动态库,而非系统的动态库 ,像苹果的libSystem.B.dylib或者Foundation等动态库已经在共享缓存里,系统已经做了最大优化,我们无需处理,而对于我们自己制作的动态库,苹果建议不要超过6个;
  • 对于Objc setup time 加载时间的优化: 它做的事情是注册Objc所有的类,相当于在可执行文件Mac-o中读取所有的类,把他们放到一张表里面去,这些类就能被我们所使用,包括runtime动态添加一个类,它都需要register函数做注册操作。

因此,我们所有使用到的类在系统加载时候都有一个注册的过程,所以,不管我们写的类最终有没有使用到,它都会有注册操作,这最终会影响到 程序启动的时间。

如何检测项目中有哪些类没有被用到?

  • 打开终端,运行下面的命令:
gem install fui
  • 查看是否成功:
fui help

如何使用?

拿一个测试项目举例, 在项目点击到终端:

Pasted Graphic 7.png

就可以找到项目中没有使用的类

Pasted Graphic 8.png

  • 这样就可以根据需要删除一些一直没有使用的类文件(删之前请做好备份,主要针对老项目或者那些很多人接手的);
  • 还有一些方式可以查看哪些类是没被使用过,但是大部分类都是有用的,所以其实对于启动优化提升可能不是很明显,这就是setup阶段的优化;
  • 另外,github有一个项目LSUnusedResources-master,可以清楚未使用的图片,减少包体积。

Pasted Graphic 9.png

运行完会看到软件界面:

Pasted Graphic 11.png

选择项目路径,点击search会出现没有使用的图片资源

Pasted Graphic 10.png

这对打包app体积优化有帮助,尤其是老项目

在ObjC setup time这个阶段之后,还有一个initializer time,在这个阶段主要是各种初始化操作,比如初始化ObjC类的load函数,执行 C++的构造函数,load函数加载会在main执行之前,所以尽量不要在load里面做一些耗时操作,它都会影响app的启动时间。

符号绑定

再回过头来说一下rebase/binding time binding就是符号绑定,这里拿一个小的可执行文件举例:

Pasted Graphic 12.png

xcrun nm -nm main

得到:

Pasted Graphic 13.png

最上面两个(undefined) 未定义里面的 binder就是用来符号绑定的,在进行静态链接的时候,printf函数会被标记来自libSystem动态库, 但是只知道哪个库是没有用的,需要知道函数的切确地址,一旦执行了_printf函数binder就会把libStystem里面函数真正实现的地址和_printf这个符号进行绑定,调用_printf能够真正的去执行函数的实现,这个过程就是binding符号绑定。

虚拟内存

另一个rebase,他是做指针的修复(翻译),这里涉及到虚拟内存的概念。