iOS启动优化之二进制重排

324 阅读6分钟

0a000581fca74098bc53b876f5e957ae.jpeg 今天我们来探索优化app启动有哪些方案。

一.  启动优化

APP启动分为两个阶段 1.pre-main,main()函数之前,系统加载可执行文件,加载动态链接dyld,dyld递归加载所有依赖的动态库,然后dyld调用起main()函数。

1、pre_main

image.png DYLD_PRINT_STATISTICS=1这个参数在Xcode13起就无法使用了。这里用xcode12运行打印: image.png

1.dylib loading:加载动态库。可以通过减少动态库的数量来优化这一部分所消耗的时间。苹果的建议是一个项目里面自己制作的动态库的动态库数量不超过6个

2.ObjC setup:注册Objc类,进行selector唯一性检测等。可以通过减少Objc类的数量,减少selector的数量来进行优化。

3.rebase/binding:rebase指针修复,binding符号绑定。这一步的优化手段和第二步一样。

4.initializer:各种初始化的操作,比如执行objc的+load函数,C++的构造函数等。不要在+load函数里面做一些耗时的操作,或者把一些操作延时的放在+initialize里面去执行。

main,从main()函数开始到执行完appDelegate的didFinishLaunchingWithOptions方法展示首页数据。

优化手段有:

1.少使用xib和storyboard。
2.删除NSLog打印。
3.整理didFinishLaunchingWithOptions方法里面的业务逻辑,可以异步请求的异步请求,可以延时加载的延时加载。

1、工具篇

安装fui:(fui github地址

gem install fui

//获取帮助
fui help

//使用 find命令操作项目中未使用的文件class
fui find

//在任何路径中查找未使用的类
fui --path=~/source/project/Name find

在ObjC setup阶段可以使用fui find查找项目中未被使用的类文件,进行优化项目。

还可以通过如下python命令:

python FindSelectorsUnrefs.py -a xxx/xxx.app -p /path/xxxx/xxx/xxx/test

image.png 来找到项目中没有使用的方法

使用LSUnusedResources-master工具可以找出工程内部未使用的图片。 image.png

xcrun nm -nm main //运行代码

image.png 看到dyld_stub_binder在libSystem中,这里进行了符号绑定,找到_printf的地址在和符号进行绑定。

2、虚拟内存

什么是虚拟内存?虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。

物理内存选址图: image.png 应用在使用内存的时候是,懒加载模式,在使用时在去开辟内存。所以会照成如图,内存不连续。需要大量的计算,来获取内存地址,照成应用卡顿。所以为了解决这一问题,就设计出了虚拟内存的概念了。

虚拟内存表选址图: image.png CPU+MMU(内存管理单元)作用是把虚拟地址翻译成物理地址。翻译时是一块一块的翻译的,4kb或者16kb。用page来表示这个块,就有了内存分页表。也保证了应用使用内存的安全性。

如果这个时候进程1去使用P2的内存地址会发生什么?MMU去翻译内存地址时,发现在物理地址中还没有加载出P2的物理地址,就会产生缺页中断(page fault)。在使用时就会开始加载P2,如果物理内存没有可用的内存,就会替换掉没有使用的内存页,这个过程就叫页面置换(Linux的“交换空间”)。

如果需要进程间通信怎么解决呢?就需要依赖系统提供的api访问共享内存。如图: image.png 进程之间在访问共享缓存进而达到进程之间通信的效果。

3、rebase

首先app其实是一个二进制ipa文件,里面全是二进制元数据指针, 任何人下载下来ipa数据结构都是相同的,所以为了防止他人猜测某个特定功能在内存中的位置,苹果会运用地址空间布局随机化技术ASLR(Address Space Layout Radomization )来给指针的起始地址一个随机的偏移量,而dyld任务之一就是重定位二进制ipa文件中的元数据指针指向,纠正起始量。

具体做法:
1.适当用struct替换class声明
2.减少分类拓展的使用
3.swift减少@objc关键词使用
4.final修饰的包含很多属性的大类可以用struct来代替 可减少60%多重定位时间
5.改进的代码生成比如用生成函数 替换自定义类

ALSR

ASLR(address space layout randomization),地址空间布局随机化。是一种针对缓冲区益出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者接定位攻击代码位置,达到阻止益出攻击的目的的一种技术。使得可执行文件和动态库在虚拟内存中的地址在每次启动都不固定。

用MachOView打开一个app的二进制文件看看: image.png 这里偏移计算请看Hook出探索

二.  二进制重排

概念:找到程序在启动时候需要调⽤的符号,然后修改编译参数完成⼆进制⽂件的重新排布。 image.png 自己创建了一个YNWeChat项目配置上appSign.sh(这个网上也有的search)重签名脚本文件,对微信-7.0.8.ipa(这个ipa需要越狱机进行导出用到一些开壳工具)二进制文件进行重新签名。

运行NYWeChat后,开启Instruments->System Trace监测应用内存泄漏,Page Fault的情况:

image.png

LinkMap:是iOS编译过程的中间产物,记录了二进制文件的布局。可以在Xcode的Build Settings里开启Write Link Map File。 image.png 可以查看到build的时候生成了LinkMap File 如图: image.png

Order文件:编译器会按照order文件的内容,对二进制文件进行排列。可以在Xcode的Build Settings里的Order File处设置。

image.png 程序默认按照Compile Sources 的顺序来Link Map 文件的链接符号顺序。
所以我们通过自己生成配置Order File 文件重排来优化iOS启动速度。

image.png

_main
_ab123
+[AppDelegate load]
+[ViewController load]
_vip456

查看Link Map后发现,已经按照我们编写的order 文件顺序进行编译: image.png

File [4] 这个4代表 _main所在文件的编号。

二进制重排能够优化启动时间的原理:App在执行的过程中会存在大量的Page Fault,一个Page Fault的耗时很少,但是当大量的Page Fault存在时,就会影响到代码的执行速度。同理,在App启动的时候,就可能会出现大量的Page Fault。二进制重排就是把启动过程中需要使用到的符号,重新排列在一个或者几个Page里面,减少Page Fault的次数,从而达到减少启动时间的目的。 (Ps:又是朴实无华的一天)