在进入 App 的过程中,需要加载各种文件,这种文件必然是需要符合某种格式来让系统高效的进行阅读。

在 iOS 系统中,需要先进行加载的是通用二进制 (Universal Binary / Fat Binary) 文件。

通用二进制文件是一种“容器”格式,旨在解决跨架构兼容性问题(如同时支持 Intel 和 Apple Silicon Mac)。它的格式如下:

struct fat_header {
    uint32_t	magic;		/* FAT_MAGIC (0xcafebabe) */
    uint32_t	nfat_arch;	/* 包含的架构数量 */
};
struct fat_arch {
    int32_t		cputype;	/* CPU 类型 (如 ARM64, x86_64) */
    int32_t		cpusubtype;	/* CPU 子类型 */
    uint32_t	offset;		/* 该架构 Mach-O 在文件中的偏移量 */
    uint32_t	size;		/* 该架构 Mach-O 的大小 */
    uint32_t	align;		/* 内存对齐 */
};

内核一开始会先读取 读取 fat_header : 发现 0xcafebabe ,确认为 Fat Binary;然后遍历 Arch 列表 : 读取随后的 fat_arch 数组;接着通过 cputype 和 cpusubtype ,与现在正在运行的 CPU 进行比对;找到了对应 CPU ,内核则通过 offset 和 size ,讲对应架构的 Mach-O 数据映射到内存中。

这样的好处在于:内核只需要找到对应的索引,就能定位到正确的位置,加载到正确架构的代码,无关代码则完全不会干涉,节省了大量的时间和 RAM。

这是典型的通过以磁盘空间换取兼容性和运行时效率的设计。

Mach-O 格式

Mach-O (Mach Object) 是 macOS 和 iOS 上使用的可执行文件格式。它主要由三大部分组成: Header 、 Load Commands 和 Data 。

  1. Header (头部) 包含 Magic Number、CPU 架构、文件类型(如 MH_EXECUTE )、Load Commands 的数量和总大小。Runtime 使用 headerType (即 mach_header_64 ) 来解析它。

  2. Load Commands (加载命令) 告诉内核和 Dyld 如何布局内存。LC_SEGMENT_64 : 最关键的命令,划分内存区域(Segment)。LC_LOAD_DYLIB : 声明依赖的动态库(如 Foundation )。Data (数据区 - Segments & Sections)

  3. 真正的“内容仓库”。

    • __TEXT Segment : 只读 。存放代码指令 ( __text ) 和常量 ( __cstring )。
    • __DATA Segment : 可读写 。存放全局变量和 ObjC Runtime 元数据 。
      • Runtime 在初始化时 ( _objc_init -> map_images ),会通过 getSectionData 扫描这里的特定 Section:
    • __objc_classlist : 读取所有类。
    • __objc_selrefs : 读取方法选择器。
    • __objc_protolist : 读取协议。 - __LINKEDIT Segment : 只读 。存放符号表、字符串表和代码签名,供 Dyld 链接和验签使用。