iOS编译原理

iOS的编译,使用了基于LLVM的编译器。Clang(或者Swift)作为编译前端,LLVM作为后端,将代码文件编译为当前架构所对应的可执行文件

iOS编译原理

编译

编译指的是,将 一门语言(通常针对开发者) 所写的程序 转化为 目标语言(通常针对机器) 的程序 。而实现这个过程的工具则是编译器

编译器 通常分为 前端后端

  • 前端: 分析代码,生成 后端 使用的中间码。
  • 后端: 针对机器架构,生成对应的可执行机器码。
  • 前后端分离: 这样设计的好处则是,支持新语言的时候,只需要修改前端;支持新底层架构,只需改后端了。

LLVM

LLVM 是一个自由软件项目,它是一种以C++写成编译器基础设施。利用虚拟技术创造出编译时期、链接时期、运行时期以及“闲置时期”的最优化。它最早以C/C++为实现对象,而当前它已支持包括ActionScript、Ada、D语言、Fortran、GLSL、Haskell、Java字节码、Objective-C、Swift、Python、Ruby、Rust、Scala以及C#等语言。

在基于LLVM的编译器中,前端将代码进行检查(语法检查等)和解析,并生产中间代码LLVM IR。该IR选择对应的类型,对代码进行优化,然后被发送到代码生成器,根据本机的架构生成对应的机器代码。

Clang

Clang 是一个C、C++、Objective-C和Objective-C++编程语言的编译器前端。它采用了LLVM作为其后端,而且由LLVM2.6开始,一起发布新版本。它的目标是提供一个GNU编译器套装(GCC)的替代品,支持了GNU编译器大多数的编译设置以及非官方语言的扩展。

Clang项目包括Clang前端和Clang静态分析器等.

Clang作为编译前端,完成 语法分析,语义分析,生成中间代码 等工作。比如我们常见的语法警告和错误提示就在这一过程中完成。

编程语言类型

  • 编译语言 编译语言在执行的时候,通过编译器生成机器码,机器码直接在CPU上执行。
    • 例如: Objective-C, Swift, C/C++ 等
  • 直译式语言 直译式语言不需要经过编译的过程,而是在执行的时候通过一个中间的解释器将代码解释为CPU可以执行的代码。
    • 例如: JavaScript

iOS编译

iOS 编译采用了 Clang+LLVM 的方式。Clang作为前端,LLVM作为后端。

简单的文件编译过程: (Swift的话,前端则不是Clang,而是Swift)

  • 代码文件 =>
    • Clang
      1. 预处理(Preprocessor)
        • 宏的替换, 头文件的导入, 删除注释等
      2. 词法分析(lexical anaysis)
        • 读入源程序的输入字符,将它们组成词素,生成并输出一个词法单元序列(Token)的过程。
      3. 语法分析(semantic analysis)
        • 将Token生成抽象语法树(AST)
        • 对这个树进行分析,找出代码中的错误。
      4. 生成中间代码(LLVM IR)
        • 将语法树自顶向下遍历逐步翻译成 LLVM IR(中间码)
    • LLVM Optimizer
      1. 选择对应的类型
    • LLVM Code Generator
      1. 优化IR
      2. 生成Target相关汇编码
      3. 汇编器以汇编代码作为输入,将汇编代码转换为机器代码
      4. 连接器把编译产生的.o文件和(dylib,a,tbd)文件Link,生成一个mach-o文件
  • => Mach-O格式的可执行文件

mach-o文件 是记录编译后的可执行文件,对象代码,共享库,动态加载代码和内存转储的文件格式。不同于 xml 这样的文件,它只是二进制字节流,里面有不同的包含元信息的数据块,比如字节顺序,cpu 类型,块大小等。文件内容是不可以修改的,因为在 .app 目录中有个 _CodeSignature 的目录,里面包含了程序代码的签名,这个签名的作用就是保证签名后 .app 里的文件,包括资源文件,Mach-O 文件都不能够更改。

Mach-O 文件包含三个区域:

  • Mach-O Header:包含字节顺序,magic,cpu 类型,加载指令的数量等
  • Load Commands:包含很多内容的表,包括区域的位置,符号表,动态符号表等。每个加载指令包含一个元信息,比如指令类型,名称,在二进制中的位置等。
  • Data:最大的部分,包含了代码,数据,比如符号表,动态符号表等。

完整全过程:

  1. 编译信息写入辅助文件,创建文件架构 .app 文件
  2. 把Entitlements.plist写入到DerivedData里,处理打包的时候需要的信息(比如application-identifier)。
  3. 处理文件打包信息
  4. 执行 CocoaPod 编译前脚本,checkPods Manifest.lock
  5. 编译.m文件,使用 CompileC 和 clang 命令
  6. 链接需要的 Framework
  7. 编译 xib
  8. 拷贝 xib ,资源文件
  9. 编译 ImageAssets
  10. 处理 info.plist
  11. 执行 CocoaPod 脚本
  12. 拷贝标准库
  13. 创建 .app 文件和签名

xcode中的编译设置

编译配置信息都存储在这个文件(.xcodeprog)里,例如CocoaPod就是通过修改(.xcodeprog)文件来完成相关第三方库的打包链接的。

Build Phases

  1. Target Dependencies

    是用来指定编译顺序的。
    是指需要先编译好Target Dependencies中的库,
    才能编译当前Target。显式声明了依赖关系。

  2. Compile Sources

    需要编译的的代码文件(.m文件)

  3. Link Binary With Libraries

    项目中的静态库和动态库,它们会和编译生成的目标文件进行链接

  4. Copy Bundle Resources

    打包资源文件(StoryBoard,Xib,Strings,Assets等)

  5. [CP]Check Pods Manifest.lock

    用来检查cocoapod管理的三方库是否需要更新(在引入Pod后才有这个选项)[CocoaPod的自定义脚本,所有前面有CP的都是。]

Build Rules

Build Rules 指定了不同的文件类型如何处理以及输出在哪。一般来说,开发者并不需要修改这里面的内容。如果你需要对特定类型的文件添加处理方法,那么可以在此处添加一条新的规则。

Build Settings

Build Settings 指定了在 build 的过程中各个阶段的选项的设置,配置每个任务的详细内容.

Linker把编译器编译生成的多个文件,链接成一个可执行文件。

通常链接器的输入为:

  • .o 文件
  • 动态库
  • 静态库
  • 只包含符号的库文件(符号是一段代码或者数据的名称)

Clang Module

从上面的内容可知,我们在导入头文件的时候,通常预处理器在处理的时候会把这一行替换成对应头文件的文本。

但是这种直接替换,在某些时候会产生一定的问题:

  • 当头文件过多的时候,会产生大量的预处理消耗
  • 当头文件替换的时候,不同的头文件里面的宏定义可能会因为命名一样而被覆盖
  • 边界不明显。拿到一组.a和.h文件,很难确定.h是属于哪个.a的,需要以什么样的顺序导入才能正确编译。

clang module不再使用文本模型,而是采用更高效的语义模型。clang module提供了一种新的导入方式:@import,module会被作为一个独立的模块编译,并且产生独立的缓存,从而大幅度提高预处理效率。

在Target的build setting中,设置define module为YES,则可以启动Clang Module。

#import <Foundation/NSString.h>的时候,编译器会检查NSString.h是否在一个module里,如果是的话,这一行会被替换成@import Foundation

World Mythology - 创世神话 电幻国度

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×