前言
- iOS程序的启动与
dyld
息息相关,如果想要优化启动速度,可以先了解一下dyld的启动原理。dyld 加载App流程源码分析 - iOS的启动分为
冷启动
和热启动
冷启动 :第一次打开app或app被杀死后重新打开叫冷启动(走didFinishLaunchWithOptions方法)
热启动:app在后台且存活的状态下,再次打开app叫热启动(不走didFinishLaunchWithOptions方法)
我们一般说的app
启动优化,是指冷启动
优化。如果我们不了解dyld,我们一般以为app的第一调用的函数是main
函数。实际上main函数的调用也是有dyld
调用的。
app启动分为main函数之前,也就是premain阶段
,和main函数
之后(app的业务逻辑)
premian(main)函数之前
直接去看dyld的源码,可能有点繁琐。苹果也给我们提供了以环境变量DYLD_PRINT_STATISTICS
,通过在xcode设置这个环境变量。通过这个环境变量我们就可以看到dyld启动应用程序的各个模块所消耗的时间:设置如下
查看打印结果:
可以看出dyld启动分为几个阶段
dylib loading time
动态库加载时间,苹果推荐我们动态库不要超过6个,如果大于6个,多个动态库合并。所以减少动态库的使用,有助于优化启动时间。rebase/binding
rebase:程序加载到内存中都会随机的分配一个基地址,这个地址我们称之为ASLR,每一个地址都会加上这个ASLR,才是真正在内存中运行的地址。
binding:应用程序可能会用到外部的符号,比如NSLOG等方法。第一次调用该方法的时候能通过符号去找指针,并绑定。第二次访问的时候就是真正的地址。
Objc setup time
OC类的注册。在这个过程中主要做了一些耗时的操作
- 读取macho data,找到oc相关的信息
- 注册oc类的时候,会有一张全局的映射表,里面存储了类名和类映射关系,sel和imp一些相关的信息。可能还有一些分类。
所以如果想在这个模块优化启动时间,如果项目很大,可以删除一些不用的类。
initializer time
会执行+load
方法,c++构造函数
,用时。所以在项目中,我们不要在这些方法里面做一些延迟加载的事情。
总结:上面都优化都是在毫秒级别,所以启动优化大部分还是我们自己代码的业务逻辑优化。将耗时的操作放到子线程去做处理.
物理内存和虚拟内存
在早期,我们的计算机都是直接通过物理内存加载应用,但是随着不断的发展,发现这样会存在安全问题和效率问题。为了解决这一问题,系统工程师就引入了虚拟内存的概念。具体的可以参考物理内存和虚拟内存。
- 物理内存加载方式
早期计算机都是直接在物理内存加载进程
- 虚拟内存原理 虚拟内存可以认为是我们人为的创造了一块连续的内存地址空间,但是实际上这个内存地址只是一个虚拟地址,而这个虚拟地址通过一张映射表映射后才可以获取到真实的物理地址。其原理图如下:
虚拟内存分页
- 我们一般在使用一个应用的时候,并不是使用所有的功能。这样就把一个应用程序分成很多页,需要使用到哪一部分功能的时候,再把对应页加到内存中,也就是懒加载。这样就能大大的节约内存的使用空间。
Mac OS
内存4kb
一页,iOS
是16kb
一页。可以使用PAGESIZE
命令,在终端直接查看
分页加载的原理图:
- 0表示已经加载到内存,1表示没有价值到内存
- 缺页中断:但内存需要访问某一个模块时候,比如说
进程1
的P2
页。但是P2
并不在物理内存中,系统就会阻塞改进程,这样就会触发一个缺页中断
,page fault
。 - 当一个缺页中断触发,系统就会重新从磁盘读取改页到物理内存中,并且建立该页从虚拟内存到物理内存的映射表。如果内存满了,就会用页面置换方法把旧页面覆盖掉。
总结:从上面的分页原理,我们可以得到一个思路。如果我们在应用启动的时候减少page fault,是不是就可以优化一点启动时间呢?
page fault 调试
- 启动Instruments,使用Xcode选择要检测的app,打开System Trace
- 点击录制
- 在搜索框输入 main thread,选择Virtual Memory
- File Backed page In 就是缺页中断的次数456,平均每场耗时200.01us。
- 这个项目是一个demo项目,比较小。如果项目很大,启动的时候加载的模块比较多,那么page falut就会很多。这样就会影响启动时间
二进制重排
- 二进制重排能狗优化启动时间的最主要原因就是重新排列方法符号的顺序,使启动相关的方法排到最前面从而减少
page fault
的次数 - Link Map File通过这个,我们可以查看符号的顺序。
- Xcode提供了排列符号的设置给开发者,设置
order_file
即可. - 如何知道我们的app在启动的时候调用了那些符号呢,可以采用clang 插桩,能够拿到
swift
、oc
、c
、block
所有符号
具体二进制的操作可以参考下一篇博客二进制重排(clang插桩)