大家好!我是OB!
正文开始前,先说说有的没的!
一、静态库和动态库
静态库 | 动态库 | |
---|---|---|
格式 | .a 和 .framework(Mach-O Type为 Static Library) | .tbd(以前是.dylib) 和 .framework(Mach-O Type为 Dynamic Library) |
链接时 | 被完整的复制到可执行文件中,多次使用就会多份拷贝 | 不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存。 |
优点 | 1:编译后的执行程序不需要外部的函数库支持,因为所有使用的函数已经被编译进去了。相对于动态库,执行效率更高 | 1:动态库在编译的时候,并没有被编译近目标代码,你的程序执行到相关函数是才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小 。2:动态函数库的改变并不影响程勋,所以动态函数库的升级比较方便 。 |
缺点 | 1:利用静态函数库编译成的文件比较大 ,因为整个函数库所有的数据都会被整合进目标代码中。2:静态函数库改变了,那么你的程序必须重新编译 。 | 1:程序执行到相关函数时才进行调用,程序运行环境中必须提供相应的库 ,2:因为不是预先加载所以在链接函数的时候需要做大量的工作,相对来说比较耗时 |
二、Mach-O文件
Mach-O为Mach Object文件格式的缩写,它是一种用于可执行文件,目标代码,动态库,内核转储的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性,并提升了符号表中信息的访问速度。
App可执行文件.o和系统dylib(动态链接库)都是Mach-O文件。
三、APP启动过程
进入主题:当我们点击icon到第一个页面展示完成,发生了什么样的过程?这个过程就是APP启动过程,也就是冷启动!
APP启动总时间 = mian()之前 + main() 之后;
1:main()函数之前
程序要想运行,必须要有对应的运行环境,那么这个过程就是搭建程序运行的环境。
App开始启动后,系统的dynomic loader
首先加载可执行文件(.o文件),然后 dyld从可执行文件的依赖开始, 递归加载所有的依赖动态库(Dylib
)。
dyld
:dynomic loader
动态链接器,作用是加载一个进程所需要的可执行文件,dyld是开源的
第一步:加载动态库
动态库包含:用到的所有系统 framework,以及一些libxxx的库,比如runtime的运行库:libobjc,运行GCD的库:libdispatch()。
加载动态库其实就是加载动态库的mach-o文件,主要加载过程如下:
- 找到动态库的mach-o文件
- 打开并验证文件
- 在系统注册文件
- 调用对应的各种启动函数
由于该过程也是需要时间处理的,因此可以如下优化:
- 不用或者少用非系统库
- 合并非系统库
- 使用静态库,只用到库的单一功能可以写成代码放入程序
总之:dyld 将 image(镜像:各种Mach-O文件) 加载到内存后,dyld 会通知Runtime进行下一步的处理
第二步:Runtime阶段
Runtime收到dyld的通知,就开始工作调用_init_objc()
函数。开始解析内存中的images(镜像)
- 注册Objc类 (class registration),初始化类,元类对象
- 把category中的方法插入主类方法列表 (category registration)
- 调用Objc的+load()函数
- C++的初始化方法
- 非基本类型的C++静态全局变量的创建(通常是类或结构体)
可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)就会加载到内存中。
runtime处理完成后,runtime 的那些方法(动态添加 Class、swizzle 才能生效)。
由此可以做如下优化:
- Objc类数量和 selector数量越少越好,删除无用(没用到)的类和方法
- 减少C++虚函数数量
- 使用swift stuct:减少符号的数量
通过对Mach-O文件的了解,可以知道__TEXT:__objc_methname:中包含了代码中的所有方法,而__DATA__objc_selrefs中则包含了所有被使用的方法的引用,通过取两个集合的差集就可以得到所有未被使用的代码
总结一下:对于main()调用之前我们可以如下优化:
- 少用framework,是否可以合并非系统库,因为动态链接比较耗时
- check framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
- NSObject类数量和 selector数量越少越好,删除无用(没用到)的类和方法
+load()
方法尽量延后调用,或者放在initialize()
方法中执行- 尽量不要用C++虚函数(虚函数指针与虚函数表。其中创建虚函数表有开销)
虚函数:通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。虚函数virtual 可以访问。但是virtual需要用到虚函数指针与虚函数表,而创建虚函数表有开销。
2:main()之后
main()之后 主要工作就是初始化必要的服务,显示首页内容等。主要优化
Application:didFinishLaunchingWithOptions:
方法
- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOption {
//code ...
}
此时优化的方向主要是业务逻辑的优化
- 一些与UI展示无关的业务做延迟加载,比如各种认证,检测,注册等
- NSUserDefaults,FMDB等数据是否可以延后或者分开读取
四、共享缓存(shared cache)技术
dyld加载时,为了优化程序启动,启用了共享缓存(shared cache)技术。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O镜像加载时,dyld首先会检查该Mach-O镜像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能是有明显提升的。
1:热启动
如果程序刚刚被运行过,那么程序的代码会被dyld缓存,所以当我们kill这个APP时,再次重启加载时间也会相对快一点,也就是热启动;
2:冷启动
长时间没有启动或者当前dyld的缓存已经被其他应用占据,那么又变成冷启动了