iOS 底层系列 - 启动的过程

657 阅读5分钟

APP的启动优化

Main 函数之前

  • 移除不需要的 动态库.
  • 移除不需要 Objec 类, 减少selector数量 。
  • 合并功能类似的类和扩展(Category)少用分类。
  • 减少 C++ constructor 构造函数数量。
  • 减少 ObjeC +load ,尽量用 dispatch OnceInitializers 代替。

Main 函数之后

  • 建立启动监控组件,监控启动任务。
    • 划分主线程,子线程,和延时执行等,上报启动时间(启动耗时)
  • 压缩资源图片,删除无用的图片(IO操作)
  • 使用 Instagram 的 Time Profiler 进行分析。
  • 避免在用户看到的第一个界面(首页控制器或注册登录页面)的viewDidLoad和viewWillAppear做太多事情。
  • 首页控制器或注册登录页面用纯代码方式来构建,不用 aulayout。

APP启动时间的优化,主要是针对冷启动进行优化。

一、APP的启动介绍

冷启动与热启动

  • 冷启动(Cold Launch):
    • 从零开始启动APP。
  • 热启动(Warm Launch):
    • APP已经在内存中,在后台存活着,再次点击图标启动APP

打印APP的启动时间分析

  • 通过添加环境变量可以打印出APP的启动时间分析(Edit scheme -> Run -> Arguments) DYLD_PRINT_STATISTICS 设置为 1
  • 如果需要更详细的信息,那就将DYLD_PRINT_STATISTICS_DETAILS设置为1

Pre-main 的大概过程

Pre-mian 大概过程主要分为:

  • Load dylibs (开始将程序二进制文件初始化) 当我们点击 App 图标,内核就开始做启动程序的初始化,然后交给 dyld(The Dynamic Link Editor,动态链接器); dyld 首先会读取镜像文件,然后递归的查找动态库,利用 ImageLoader 来将其 加载到内存中

  • Rebase

  • Bind 但是由于 ASLR 的特性,这里需要 Rebase/Bind 修复镜像中的资源指针,来指向正确的地址

  • Objc runtimedyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理 ImageLoader 已经将对应的镜像加载到内存,这个时候 Runtime 会调用 map_image 去解析和处理该镜像资源(注册 Objc 类、处理 category 等

  • Initializers 接下来再调用 load_image,遍历调用类的 load 方法、调用C++的构造函数属性函数、创建非基本类型的C++静态全局变量等等。

二、APP的冷启动

1. APP的冷启动3大阶段

  • dyld
    • 加载动态库二进制
  • runtime
    • 进行各种 objc结构的初始化(注册Objc类 、初始化类对象,执行+load 等等)
  • main
    • 执行 OC 代码。didfinish。
总结
  • APP的启动由 dyld主导 ,将可执行文件加载到内存,顺便加载所有依赖的 动态库 , 并由runtime负责加载成objc定义的结构
  • 所有初始化工作结束后,dyld就会调用 main函数
  • 接下来就是UIApplicationMain函数,AppDelegate的 application:didFinishLaunchingWithOptions:方法。

2. APP的启动 - dyld

(1) 介绍 dyld
  • dyld(dynamic link editor),Apple的动态链接器,可以用来装载Mach-O文件(可执行文件动态库等)
  • 系统 kernel 做好启动程序的初始准备后,交给 dyld 负责。
(2) 启动APP时,dyld所做的事情 :
  1. 装载APP的可执行文件,同时会递归加载所有依赖的动态库
  2. 当dyld把可执行文件、动态库都装载完毕后,会通知Runtime进行下一步的处理
加载的动态库,例如以下:
  • CoreGraphics.framework
    • 绘图系统,常用于绘制自定义视图,纯C的API,使用Quartz2D做引擎。
  • UIKit.framework
    • 常用的视图框架,封装度最高,都是OC对象,处理与用户的交互。
  • Foundation.framework
    • Foundation框架定义了Objective-C类的基础层
  • CoreFoundation.framework
    • Core Foundation框架和Foundation框架紧密相关,它们为相同功能提供接口,但Foundation框架提供Objective-C接口。
  • libobjc.A.dylib ( objc 和 runtime
    • libauto.dylib
    • libc++abi
  • libSystem.dylib
    • libdispatch ( GCD )
    • libsystem_c ( C语言库 )
    • libsystem_blocks ( Block )
    • libcommonCrypto ( 加密库,比如常用的 md5 函数 )

3. APP的启动 - runtime

  • Runtime 是 Objective-C 运行时。

  • libSystem 是若干个系统 lib 的集合,所以它只是一个容器 lib 而已,而且它也是开源的,里面实质上就一个文件 init.c

  • 由 libSystem_initializer 逐步调用到了 _objc_init,这里就是 objc 和 runtime 的初始化入口

三、总结

总结 1

  1. dyld 开始将程序二进制文件初始化

    • 交由 ImageLoader 读取 image,其中包含了我们的类、方法等各种符号。
  2. 由于 runtime dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理。

  3. runtime 接手后调用 map_images 做解析和处理,接下来 在load_images中调用call_load_methods,调用所有Class和Category的 +load 方法.

  4. 调用C++静态初始化器和__attribute__((constructor))修饰的函数

  5. 进行各种 objc结构的初始化(注册Objc类 、初始化类对象等等)

当这一切都结束时,dyld 会清理现场,将调用栈回归,只剩下:孤独的 main 函数

总结 2

1. App启动过程

  • 解析Info.plist

    • 加载相关信息,例如如闪屏
    • 沙箱建立、权限检查
  • Mach-O加载

    • 如果是胖二进制文件,寻找合适当前CPU类别的部分

    • 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)

    • 定位内部、外部指针引用,例如字符串、函数等

    • 执行声明为__attribute__((constructor))的C函数

    • 加载类扩展(Category)中的方法

    • C++静态对象加载、调用ObjC的 +load 函数

  • 程序执行

    • 调用main()
    • 调用UIApplicationMain()
    • 调用applicationWillFinishLaunching

总结 3

汇总