app启动
先来看app启动流程
APP的启动可以分为2种
1、冷启动(Cold Launch):从零开始启动APP
2、热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP
APP启动时间的优化,主要是针对冷启动进行优化
通过添加环境变量可以打印出APP的启动时间分析(Edit scheme -> Run -> Arguments)
1、DYLD_PRINT_STATISTICS设置为1
2、如果需要更详细的信息,那就将DYLD_PRINT_STATISTICS_DETAILS设置为1
APP的冷启动概括为三大阶段
dyld,Apple的动态链接器,可以用来装载Mach-O文件(可执行文件、动态库等)
启动APP时,dyld所做的事情有
1.装载APP的可执行文件,同时会递归加载所有依赖的动态库
2.当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理
runtime
启动APP时,runtime所做的事情有
1.调用map_images进行可执行文件内容的解析和处理
2.在load_images中调用call_load_methods,调用所有Class和Category的+load方法
3.进行各种objc结构的初始化(注册Objc类 、初始化类对象等等)
4.调用C++静态初始化器和attribute((constructor))修饰的函数
到此为止,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被runtime 所管理
main
1.APP的启动由dyld主导,将可执行文件加载到内存,顺便加载所有依赖的动态库
2.并由runtime负责加载成objc定义的结构
3.所有初始化工作结束后,dyld就会调用main函数
4.接下来就是UIApplicationMain函数,AppDelegate的application:didFinishLaunchingWithOptions:方法
优化方案
dylib loading
从主执行文件的 header 获取到需要加载的所依赖动态库列表,而 header 早就被内核映射过。然后它需要找到每个 dylib,然后打开文件读取文件起始位置,确保它是 Mach-O 文件。接着会找到代码签名并将其注册到内核。然后在 dylib 文件的每个 segment 上调用 mmap()。应用所依赖的 dylib 文件可能会再依赖其他 dylib,所以 dyld 所需要加载的是动态库列表一个递归依赖的集合。一般应用会加载 100 到 400 个 dylib 文件,但大部分都是系统 dylib,它们会被预先计算和缓存起来,加载速度很快。
优化操作
减少动态库、合并一些动态库(定期清理不必要的动态库)
减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类)
减少C++虚函数数量
Swift尽量使用struct
rebase/bind
由于ASLR(address space layout randomization)的存在,可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不固定,所以需要这2步来修复镜像中的资源指针,来指向正确的地址。 rebase修复的是指向当前镜像内部的资源指针; 而bind指向的是镜像外部的资源指针。
rebase步骤先进行,需要把镜像读入内存,并以page为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在IO。bind在其后进行,由于要查询符号表,来指向跨镜像的资源,加上在rebase阶段,镜像已被读入和加密验证,所以这一步的瓶颈在于CPU计算。
通过命令行可以查看相关的资源指针:
xcrun dyldinfo -rebase -bind -lazy_bind myApp.App/myApp
优化该阶段的关键在于减少__DATA segment中的指针数量。我们可以优化的点有:
减少Objc类数量, 减少selector数量
减少C++虚函数数量
转而使用swift struct(其实本质上就是为了减少符号的数量)
二、runtime
这一步的主要工作有
1.注册Objc类 (class registration)
2.把category的定义插入方法列表 (category registration)
3.保证每一个selector唯一 (selctor uniquing)
由于之前2步骤的优化,这一步实际上没有什么可做的。
用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++静态构造器、ObjC的+load
三、main()
关于mian()
准备阶段 这里主要是图片的解码
布局阶段 首页所有UIView的- (void)layoutSubViews()运行
绘制阶段 首页所有UIView的- (void)drawRect:(CGRect)rect运行
再加上启动之后必要服务的启动、必要数据的创建和读取,这些就是我们可以尝试优化的地方
因此,main()函数调用之前我们可以优化的点有:
1.不使用xib,直接视用代码加载首页视图。
2.NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,如果文件太大的话一次能读取到内存中可能很耗时,这个影响需要评估,如果耗时很大的话需要拆分(需考虑老版本覆盖安装兼容问题)。
3.每次用NSLog方式打印会隐式的创建一个Calendar, 仅仅针对内测版输出log。
4.梳理应用启动时发送的所有网络请求,统一在异步线程请求。
并行初始化各个业务。
5.launcherImage图片尽量小,实测这个大小会影响启动速度
6.对于MainThread有需要的业务,提供mainThread 支持。
网友评论