[译]理解 Xcode 构建系统

每一个 Swift 程序在真实设备上运行前都要经历一系列转换。这个过程通常是由 Xcode 构建系统处理的。在这篇文章中我们将了解 Xcode 构建系统的各个部分。

问题描述

任何计算机系统都包含两方面:即 软件硬件

硬件 是计算机的物理部分,例如显示器、键盘。 硬件 通常由 软件 控制, 软件 是指导硬件如何工作地一系列指令集合。软件负责编排过程,硬件负责实际执行工作,缺一不可。

作为软件工程师,我们主要关注软件部分。然而,硬件并不能直接理解使用 Swift 编写的代码,它只能接收电荷形式的指令,包含两个级别,分别称作*‘逻辑 0’ ‘逻辑 1’*。

这里就有个问题 :“如何将 Swift 代码转换成硬件能接受的形式”? 答案是 语言处理系统

语言处理系统

语言处理系统 是一系列程序的集合,这些程序可以从一组用任意源语言编写的指令中生成可执行程序。这样就允许程序员使用高级语言而不用去写机器代码,大大降低了编程复杂度。

我们在 iOS 或 macOS 开发中日常使用的语言处理系统就称作 Xcode 构建系统

Xcode 构建系统

Xcode 构建系统 的主要目标是协调各种不同任务的执行,最终生成一个可执行程序。

Xcode 运行许多工具,并在它们之间传递数十个参数,处理执行顺序、并行性等等。这肯定不是你在编写下一个 Swift 项目时想要手动处理的。

多数语言处理系统,包括 Xcode 构建系统 ,都包含 5 个部分:

  • Preprocessor(预处理器)
  • Compiler(编译器)
  • Assembler(汇编器)
  • Linker(链接器)
  • Loader(加载器)

它们通过如下图所示方式协作:

让我们仔细了解一下各个步骤。

Preprocessing 预处理

预处理步骤的目的是将程序转换为可以被提供给编译器的形式。它将宏替换为具体定义,发现依赖项并解析预处理器指令。

考虑到 Swift 编译器中没有预处理器,所以不允许在 Swift 项目中定义宏。尽管如此, Xcode 构建系统 还是进行了部分补足,通过在项目构建设置中配置 Active Compilation Conditions (主动编译条件) 方式来进行预处理。

Xcode 通过低级构建系统 llbuild 来解析依赖项, llbuild 是开源的,可以在 Github 上的 swift-llbuild 页面 找到更多信息。

Compiler 编译器

编译器 是一个程序,它将一个语言的源程序映射为另一个语言中语义等效的目标程序。换句话说, 编译器SwiftObjective-CC/C++ 代码转换为机器码而不丢失前者的含义。

Xcode 使用两个不同的编译器:一个负责编译 Swift ,另一个负责编译 Objective-CObjective-C++ 以及 C/C++ 文件。

clang 是苹果官方的 C 语言家族编译器,已经开源: swift-clang

swiftc 是一个 Swift 编译器程序,被 Xcode 用于编译及运行 Swift 源代码。我冒昧地猜测你已经访问过这个链接至少一次:它位于 Swift 语言仓库

编译器 阶段如下图所示:

编译器由两个主要部分组成:前端和后端。

前端 部分将源程序分割为单独的部分,没有任何语义或类型信息,使用特定语法结构。然后编译器使用这个结构生成源程序的 中间描述(intermediate representation)前端 也会创建并管理 符号表(symbol table) ,以搜集源程序相关信息。

符号 (Symbol)是数据或代码片段的名称。

符号表 存储你所命名的变量、方法、类的名称,每个 符号 都映射到一个确定的数据块。

Swift 编译器中间描述(intermediate representation) 被称作 Swift 中间语言 Swift Intermediate Language (SIL)SIL 会被用于后续的分析及代码优化。直接从 Swift 中间语言 生成机器码是不可能的,因此 SIL 会再经过一次转换变为 LLVM 中间描述(LLVM Intermediate Representation)

后端 阶段,以上 LLVM 中间描述 会被转换为汇编码。

Assembler 汇编器

汇编器 将可读的汇编代码转换成 可重定向的机器代码(relocatable machine code) ,生成 Mach-O 文件 ,基本上就是代码与数据的集合。

上述定义中的术语:机器代码* 和 Mach-O 文件 还需要进一步解释。

机器代码 是一种数字化语言,表示一组可由 CPU 直接执行的指令。之所以命名为可重定向的,是因为不管对象文件在地址空间中何处,指令都会相对于所在空间来执行。

Mach-O 文件 是 iOS / macOS 操作系统中的一种特殊文件格式,用于对象文件、可执行文件及库。它是以一些有意义的块分组的字节流,运行于 iOS 设备上的 ARM 处理器或者 Mac 上的 Intel 处理器。

Linker 链接器

链接器 是一个计算机程序,它将不同的对象文件和库合并起来生成一个可以在 iOS 或 macOS 系统上运行的 Mach-O 可执行文件。 链接器 接收两种类型的文件作为输入,也就是来自 汇编 阶段的对象文件以及不同类型的库( .dylib , .tbd , .a )。

细心的读者可能已经注意到 汇编器链接器 都生成了一个 Mach-O 文件作为输出。这两者应该有些不同,对吧?

来自汇编阶段的对象文件并没有处理完成,其中一些包含引用其他对象文件或库的缺失部分。举个例子,如果在代码中使用了 printf 方法,是 链接器 将这个符号与实现 printf 方法的 libc 库粘合起来的。它使用 编译 阶段生成的 符号表 来解析跨不同对象文件与库之间的引用。

Xcode 中构建具有上述特性的 Swift 项目时,你可以已经发现过 “undefined symbol 未定义符号” 错误。

Loader 加载器

最后,作为操作系统一部分, 加载器 会将程序载入内存并执行。加载器分配运行程序所需内存空间并将寄存器初始化为初始状态。

总结

在软件工程中很难低估 语言处理系统 的重要性。我们可以自由选择几乎任何高级编程语言,例如 SwiftObjective-C ,而不用去编写硬件才能理解的 0 和 1 二进制代码。语言处理系统会处理其余工作,生成一个可以在 iPhone、Mac 或其他任何终端运行的可执行程序。

作为 iOS / macOS 开发者,我们日常基础工作中都在使用 Xcode 构建系统 。它的主要组件有: preprocessor 预处理器compiler 编译器assembler 汇编器linker 链接器 、以及 loader 加载器Xcode 针对 SwiftObjective-C 使用不同的编译器,对应分别是 swiftcclang

理解 Xcode 编译过程是基础知识,对于初学者和经验丰富的开发人员都非常重要。

感谢阅读

如果你觉得不错,可以在 Twitter 上关注原作者;如果发现问题或有更好建议,欢迎留言或通过 Github 与我讨论。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章