iOS编译原理和过程

编译器

先了解一下编译器相关的LLVM和Clang:

LLVM

LLVM是构架编译器(compiler)的框架系统,以C++编写而成,可以作为多种语言编译器的后台来使用,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),进行在线编译优化、代码生成。对开发者保持开放,并兼容已有脚本。LLVM的项目是一个模块化和可重复使用的编译器和工具技术的集合。LLVM是伊利诺伊大学的一个研究项目,提供一个现代化的,基于SSA的编译策略能够同时支持静态和动态的任意编程语言的编译目标。

Clang

Clang是一个C语言、C++、Objective-C、C++语言的轻量级编译器。

特点:

  • 终端用户特色:快速编译和较少内存占用;有诊断功能;兼容GCC;
  • 实用工具和应用:基础架构模块库;可满足多样的客户需求代码重构,静态分析,代码生成;
  • 内部设计与实现:一个简单的可移植的代码库;一个为C语言、Objective-C、C++、Objective-C设计的通用的语法解析器;与C/C++/Objective-C及它们的衍生语言具有高度一致性。

iOS编译

不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端。所以简单的编译过程如图:

编译过程

编译器前端的任务:进行语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。

Clang

编译器后端任务:进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。iOS的编译过程,后端的处理如下:

  • LVVM优化器会进行BitCode的生成,链接期优化等等

LLVM优化器

  • LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码

    LLVM机器码生成器

编译过程

  1. 预处理(Pre-process):替换宏、删除注释、展开头文件、产生.i文件
  2. 编译(Compliling):把之前的 .i 文件转换成汇编语言,产生 .s文件。
  3. 汇编(Asembly):把汇编语言文件转换为机器码文件,产生 .o 文件。
  4. 链接(Link):对**.o**文件中的对于其他的库的引用的地方进行引用,生成最后的可执行文件(同时也包括多个 .o 文件进行 link)。

xcode 是根据 target 分开进行编译的。首先切换到 pch 的目录下,然后设置环境变量,然后启动 clang 并进行一系列的配置。在这之后一般就会产生具体的 .o文件作为产出(一般是有多个,针对不同的平台架构分别有一个,不过一般紧接着会把这些聚合成一个通用的 library)。不同的 target 也是有编译顺序的,具体的要看 target 之间的依赖关系。

具体的编译过程
  1. 编译信息写入辅助文件,创建编译后的文件架构(name.app)

  2. 处理文件打包信息,例如在debug环境下

    1
    2
    3
    4
    5
    Entitlements:
    {
    "application-identifier" = "app的bundleid";
    "aps-environment" = development;
    }
  3. 执行CocoaPod编译前脚本

    例如对于使用CocoaPod的工程会执行CheckPods Manifest.lock

  4. 编译各个.m文件,使用CompileCclang命令

    例如:

    1
    2
    3
    4
    CompileC ClassName.o ClassName.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    export LANG=en_US.US-ASCII
    export PATH="..."
    clang -x objective-c -arch x86_64 -fmessage-length=0 -fobjc-arc... -Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot iPhoneSimulator10.1.sdk -fasm-blocks ... -I 上文提到的文件 -F 所需要的Framework -iquote 所需要的Framework ... -c ClassName.c -o ClassName.o

    这些编译命令表示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    clang是实际的编译命令
    -x objective-c 指定了编译的语言
    -arch x86_64制定了编译的架构,类似还有arm7等
    -fobjc-arc 一些列-f开头的,指定了采用arc等信息。这个也就是为什么你可以对单独的一个.m文件采用非ARC编程。
    -Wno-missing-field-initializers 一系列以-W开头的,指的是编译的警告选项,通过这些你可以定制化编译选项
    -DDEBUG=1 一些列-D开头的,指的是预编译宏,通过这些宏可以实现条件编译
    -iPhoneSimulator10.1.sdk 制定了编译采用的iOS SDK版本
    -I 把编译信息写入指定的辅助文件
    -F 链接所需要的Framework
    -c ClassName.c 编译文件
    -o ClassName.o 编译产物
  5. 链接需要的Framework,例如Foundation.framework,AFNetworking.framework,ALiPay.fframework

  6. 编译xib文件

  7. 拷贝xib,图片等资源文件到结果目录

  8. 编译ImageAssets

  9. 处理info.plist

  10. 执行CocoaPod脚本

  11. 拷贝Swift标准库

  12. 创建.app文件和对其签名

编译过程的控制

在 xcode 中可以通过 Build phases,Build settings以及 Build rules来进行控制。

Build phases:主要是用来控制从源文件到可执行文件的整个过程的,所以应该说是面向源文件的,包括编译哪些文件,以及在编译过程中执行一些自定义的脚本什么的。

Build rules: 主要是用来控制如何编译某种类型的源文件的,如果相对某种类型的原文件进行特定的编译,那么就应该在Build rules里进行编辑了。同时这里也会大量的运用一些 xcode 中的环境变量。

Build settings:是对编译工作的细节进行设定,在这个窗口里可以看见大量的设置选项,从编译到打包再到代码签名都有,这里要注意 settings 的 section 分类,一般通过右侧的 inspector 就可以很好的理解选项的意义了。