理解 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 编译器

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

Xcode 使用两个不同的编译器:一个负责编译 Swift ,另一个负责编译 Objective-C 、 Objective-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 加载器

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

总结

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

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

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

举报
评论 0