前言
计算机语言分为机器语言、汇编语言和高级语言。
可以将高级语言分为两种:编译语言和解释型语言(直译式语言)。
解释型语言(逐步进行解释执行)
解释语言编写的程序在每次运行时都需要通过解释器对程序进行动态解释和执行,即解释一条代码,执行一条代码
。
优点:可移植好,因为只需要各种系统有解释器便可运行,不需要乱七八糟的系统库支持
缺点:执行速度慢,因为相比直接执行,多了一个翻译过程。
典型语言:php,javascript
编译型语言(一次性翻译)
编译型语言的程序只要经过编译器编译之后,每次运行程序都可以直接运行
,不需要再次“翻译”。
优点:执行速度快
缺点:可移植性差,因为编译需要对操作系统的库做出链接,所以程序运行时需要用到特定的系统库
典型语言:objective-c,swift
编译链接过程
- 预处理:处理macro 宏(如#define), import 头文件替换及处理其他的预编译指令,产生.i文件。(都是以#号开头)
- 编译:把预处理完的一系列文件进行一系列词法、语法、语义分析,并且优化后生成相应的汇编代码,产生.s文件。
- 汇编:汇编器将汇编代码生成机器指令,输出目标文件,产生.o文件。(根据汇编指令和机器指令的对照表一一翻译就可以了)
- 链接:在一个文件中可能会到其他文件,因此,还需要将编译生成的目标文件和系统提供的文件组合到一起,这个过程就是链接。经过链接,最后生成可执行文件。
经过编译和链接,才会把写的代码转换成计算机能识别的二进制指令。
iOS编译是啥?
编译其实是一个用代码解释代码的过程。在 Objective-C 和 Swift 的编译过程中,用来解释代码的,使用的是Low Level Virtual Machine 的编译器开发工具套件(LLVM)。简单的说,LLVM 是一个项目,其作用就是提供一个广泛的工具,可以将任何高级语言的代码编译为任何架构的 CPU 都可以运行的机器代码。它将整个编译过程分类了三个模块:前端、公用优化器、后端。
- 前端:对目标语言代码进行语法分析,语义分析,生成中间代码。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。我们在开发的过程中,其实 Xcode 也会使用前端工具对你的代码进行分析,并实时的检查出来某些错误。前端是针对特定语言的,如果需要一个新的语言被编译,只需要再写一个针对新语言的前端模块即可。
- 公用优化器:将生成的中间文件进行优化,去除冗余代码,进行结构优化。
- 后端:后段将优化后的中间代码再次转换,变成汇编语言,并再次进行优化,最后将各个文件代码转换为机器代码并链接。链接是指将不同代码文件编译后的不同机器代码文件合并成一个可执行文件。
clang 是 LLVM 的一个前端,它的作用是针对 C 语言家族的语言进行编译,像 c、c++、Objective-C。而 Swift 则自己实现了一个前端来进行 Swift 编译,优化器和后端依然是使用 LLVM 来完成。
Xcode 在编译 iOS 项目的时候,使用的正是 LLVM。其实我们在编写代码以及调试的时候,也在使用 LLVM 提供的功能,例如代码高亮(clang)、实时代码检查(clang)、代码提示(clang)、debug 断点调试(LLDB),这些都是 LLVM 前端提供的功能。而对于后端来说,我们接触到的就是关于 arm64、armv7、armv7s 这些 CPU 架构了。
我们的项目是一个 target,一个编译目标,它拥有自己的文件和编译规则,在我们的项目中可以存在多个子项目,这在编译的时候就导致了使用了 Cocoapods 或者拥有多个 target 的项目会先编译依赖库。
iOS编译干了啥?
- 写入辅助文件:将项目的文件结构对应表、将要执行的脚本、项目依赖库的文件结构对应表写成文件,方便后面使用;并且创建一个 .app 包,后面编译后的文件都会被放入包中。
- 运行预设脚本:Cocoapods 会预设一些脚本,当然你也可以自己预设一些脚本来运行。这些脚本都在 Build Phases 中可以看到。
- 编译文件:针对每一个文件进行编译,生成可执行文件 Mach-O,这过程涉及 LLVM 的完整流程,包括前端、优化器、后端。
- 链接文件:将项目中的多个可执行文件合并成一个文件。
- 拷贝资源文件:将项目中的资源文件拷贝到目标包。
- 编译 storyboard 文件:storyboard 文件也是会被编译的。
- 链接 storyboard 文件:将编译后的 storyboard 文件链接成一个文件。
- 编译 Asset 文件:我们的图片如果使用 Assets.xcassets 来管理图片,那么这些图片将会被编译成机器码,除了 icon 和 launchImage。
- 运行 Cocoapods 脚本:将在编译项目之前已经编译好的依赖库和相关资源拷贝到包中。
- 对包进行签名
- 完成打包
在上述流程中:2 - 9 步骤的数量和顺序并不固定,这个过程可以在Build Phases中指定。Phases:阶段、步骤,这个选项Tab在Xcode中的意思就是编译步骤。其实在Xcode中我们不仅可以设定整个编译步骤和顺序,还可以对编译规则(Build Rules)和具体步骤的参数(Build Settings)进行设定。
iOS整个编译链接过程
预处理(Prepressing)
处理macro 宏, import 头文件替换及处理其他的预编译指令,产生.i文件(都是以#号开头!
)。规则如下:
- "#define"删除并展开对应宏定义。
- 处理所有的条件预编译指令。如#if/#ifdef/#else/#endif。
- "#include/#import"包含的文件递归插入到此处。
- 删除所有的注释"//或/**/"。
- 添加行号和文件名标识,编译调试会用到。
编译(Compilation)
这个过程就是把上面的main.i文件进行:词法分析、语法分析、静态分析,优化生成相应的汇编代码,最终生成main.s文件。
- 词法分析:把源代码的字符序列分割成一个个token(关键字、表示符、字面量、特殊符号),比如把标识符放到符号表里面。
- 语法分析:生成抽象语法树AST,此时运算符号的优先级确定了;有些符号具有多重含义也确定了,比如:*是乘号还是对指针取内容;表达式不合法、括号不匹配等等,都会报错。
- 静态分析:分析类型声明和匹配问题。比如整型和字符串相加,肯定会报错。
中间语法生成:CodeGen根据AST自上向下逐步翻译成LLVM IR,并且对在编译期就可以确定的表达式进行优化,比如代码里面的a=1+3,可以优化成a=4。(假如开启了bitcode) - 目标代码生成与优化:根据中间语法生成依赖具体机器的汇编语言;并优化汇编语言。这个过程中,假如有变量且定义在同一个编译单元里,那么就给这个变量分配空间,确定变量的地址。假如变量或者函数不定义在这个编译单元里面,那就等到链接的时候才能确定地址。
汇编(Assembly)
将main.s文件编译成main.o文件。(也就是我们常说的目标文件)
这个过程就是把上面得到的main.s文件里面的汇编指令翻译成机器指令,最终生成等到main.o
链接(Linking)
这个过程就是将main.o编译成对应的Mach-O文件,也就是我们常说的可执行文件。链接的本质就是把一个或多个目标文件和需要的库(静态库/动态库,如果需要的话)组合成一个文件(Mach-O可执行文件)。