iOS +load 方法那些事儿

329 阅读3分钟

+load 方法是 iOS 开发者能够处理逻辑的最早时机。当工程中有多个 MachO(主二进制、其它动态库) 文件,其多个类、分类文件中包含 +load 方法时,+load 方法的执行顺序是怎样,经常会被拿来当做面试题。

+load 方法执行的顺序可以阅读 objc 源码获取,核心函数:prepare_load_methodscall_load_methods

这里做个总结:

  • MachO 文件之间有逻辑依赖时,先执行被依赖库;
  • MachO 文件之间无逻辑依赖时,按链接顺序执行;
  • 同一个 MachO 文件中:
    • 无逻辑关系的类按编译顺序执行;
    • 有逻辑关系的类按父类->子类->分类顺序执行;

MachO 文件链接顺序由 Other Link Flags 和 Link Binary With Libraries 决定,其中 Other Link Flags 优先于 Link Binary With Libraries 链接:

image.png

image.png

以上链接的 MachO 文件都会在 LC_LOAD_DYLIB 中:

image.png

另外苹果还提供了 DYLD_INSERT_LIBRARIES 环境变量,用于加载系统提供的工具库,这些工具在开启诊断功能或者调试、自动化测试中所需要,如 Diagnostics 中开启 Main Thread Checker:

image.png

DYLD_INSERT_LIBRARIES 环境变量中指定的动态库会优先于 LC_LOAD_DYLIB 指定的动态库,遗憾的是苹果并没有对开发者开放 DYLD_INSERT_LIBRARIES。

这也是为什么当我们在开发阶段输出动态库加载顺序时,会看到一些系统工具库在主程序 MachO 之前加载:

image.png

(one more thing:以前工具库是通过 mount 的方式挂载到 iPhone 上,最近几个系统 iPhone 直接将一些工具库内置到了 iPhone 上,无疑又增加了 iOS 系统占用的磁盘空间。)

编译顺序由 Compile Sources 决定:

image.png

如何灵活调整 +load 方法执行顺序?

实际开发中会有一些场景需要在最早执行的 +load 方法中处理逻辑,比如启动埋点、启动耗时监控、PageIn 预载等。字节、快手等公司都有发表过一些文章提到具体的方案。

字节:iOS启动优化《实战篇》 ,文中提到的方案是按照 CocoaPod 对 Pod 进行升序排列的逻辑,将 MachO 命名 AAA 开头,这里应该是参考了 A4LoadMeasure 提出的方案:

image.png

快手:快手 iOS 启动优化实践,文中提到的方案是找到最底层依赖库,让这个库依赖监控库:

image.png

以上两种方式都存在一些问题:

  • 通过 AAA 命名的方式会让语义不够清晰,不太优雅。
  • 通过增加逻辑依赖的方式会增加不必要的依赖关系。

通过查看 Xcodeproj(CocoaPods 提供用来创建或修改 Xcode 项目的 Ruby 仓库,执行 Pod 相关命令时会调用) 源码:

image.png

CocoaPods 对于 other_linker_flags 先后做了两部分处理,所以如果是 :simple 中的 MachO 文件有先添加进 list 中,顺序会在其它(:libraries、:frameworks 等) MachO 文件之前。(这里看到调用了 sort 函数,也是 执行 Pod 命令后, MachO 文件会按字母排序的原因)

那么就可以将需要的 MachO 文件加入到 [:simple] 中,来达到优先执行的目的,这里可以通过 CocoaPods 的 HOOK 实现(或者在 Build 时添加脚本实现):

post_install do |installer|
   installer.sandbox.target_support_files_root.glob("Pods-*/Pods-*.xcconfig").each **do** |xcconfig_file|
        config = Xcodeproj::Config.new(xcconfig_file)
        config.other_linker_flags[:frameworks].reject! { |flag| flag.include?('xxx1_sdk') || flag.include?('xxx2_sdk') }
        config.other_linker_flags[:simple] << '-framework "xxx1_sdk" -framework "xxx2_sdk"'
        config.save_as(xcconfig_file)
   end
end

在不改名以及不显示添加逻辑依赖的前提下,灵活调整多个 MachO 文件的 +load 执行顺序。