iOS:APP启动过程及优化方案

大家好!我是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开始启动后,系统首先加载可执行文件(.o文件),然后加载动态链接库dyld, dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。

dyld是一个专门用来加载动态链接库的库。

第一步:加载动态链接库

动态链接库包含:用到的所有系统 framework,以及一些libxxx的库,比如runtime的运行库:libobjc,运行GCD的库:libdispatch()。

加载动态库其实就是加载动态库的mach-o文件,主要加载过程如下:

  1. 找到动态库的mach-o文件
  2. 打开并验证文件
  3. 在系统注册文件
  4. 调用对应的各种启动函数

由于该过程也是需要时间处理的,因此可以如下优化:

  1. 不用或者少用非系统库
  2. 合并非系统库
  3. 使用静态库,只用到库的单一功能可以写成代码放入程序

总之:dyld 将 image(镜像:各种Mach-O文件) 加载到内存后,dyld 会通知Runtime进行下一步的处理

第二步:Runtime阶段

Runtime收到dyld的通知,就开始工作调用_init_objc()函数。开始解析内存中的images(镜像),加载images中的call_load_methods函数(调用所有Class和Category的+load方法),对Objc结构的初始化(注册Objc类,初始化类对象等等),调用c++的初始化方法。可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)就会加载到内存中。

runtime处理完成后,runtime 的那些方法(动态添加 Class、swizzle 才能生效)。

由此可以做如下优化:

  1. Objc类数量和 selector数量越少越好,删除无用(没用到)的类和方法
  2. 减少C++虚函数数量
  3. 使用swift stuct:减少符号的数量

总结一下:对于main()调用之前我们可以如下优化:

  1. 少用framework,是否可以合并非系统库,因为动态链接比较耗时
  2. check framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
  3. NSObject类数量和 selector数量越少越好,删除无用(没用到)的类和方法
  4. +load()方法尽量延后调用,或者放在initialize()方法中执行
  5. 尽量不要用C++虚函数(虚函数指针与虚函数表。其中创建虚函数表有开销)

虚函数:通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。虚函数virtual 可以访问。但是virtual需要用到虚函数指针与虚函数表,而创建虚函数表有开销。

2:main()之后

main()之后 主要工作就是初始化必要的服务,显示首页内容等。主要优化
Application:didFinishLaunchingWithOptions:方法

- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOption {
	//code ...
}

此时优化的方向主要是业务逻辑的优化

  1. 一些与UI展示无关的业务做延迟加载,比如各种认证,检测,注册等
  2. NSUserDefaults,FMDB等数据是否可以延后或者分开读取

四、共享缓存(shared cache)技术

dyld加载时,为了优化程序启动,启用了共享缓存(shared cache)技术。共享缓存会在进程启动时被dyld映射到内存中,之后,当任何Mach-O镜像加载时,dyld首先会检查该Mach-O镜像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能是有明显提升的。

1:热启动

如果程序刚刚被运行过,那么程序的代码会被dyld缓存,所以当我们kill这个APP时,再次重启加载时间也会相对快一点,也就是热启动;

2:冷启动

长时间没有启动或者当前dyld的缓存已经被其他应用占据,那么又变成冷启动了

你可能感兴趣的:(iOS进阶)