方便记忆:
- 编译语言:OC 和 Swift 基于 Clang 和 LLVM 来编译(Clang 前端、LLVM 后端)
- 特点:Clang 更快、内存占用小、兼容GCC、设计简单、模块化库(GCC 支持JAVA等和更多平台)
- 编译工作:Clang 语法分析,语义分析,生成中间代码;LLVM 机器无关的代码优化,生成机器语言
- 编译完成:生成 dSYM 文件存放函数地址映射,Fabric、友盟等崩溃解析
- 编译插入:预处理——宏、插入脚本——cocoapod
- 编译时间优化:
- 代码层—优化@class代替#import、打包库、头文件进行预编译;
- 编译器层—debug模式不生成dsym和开启Build Active Architecture并且关闭编译器优化Only
整理学习 iOS Principle 一系列的文章,每篇开头归结知识点,帮助记忆
一.相关概念
历史原因
2000年,伊利诺伊大学厄巴纳-香槟分校(University of Illinois at Urbana-Champaign 简称UIUC)这所享有世界声望的一流公立研究型大学的 Chris Lattner(他的 twitter @clattner_llvm ) 开发了一个叫作 Low Level Virtual Machine 的编译器开发工具套件,后来涉及范围越来越大,可以用于常规编译器,JIT编译器,汇编器,调试器,静态分析工具等一系列跟编程语言相关的工作,于是就把简称 LLVM 这个简称作为了正式的名字。Chris Lattner 后来又开发了 Clang,使得 LLVM 直接挑战 GCC 的地位。2012年,LLVM 获得美国计算机学会 ACM 的软件系统大奖,和 UNIX,WWW,TCP/IP,Tex,JAVA 等齐名。
Chris Lattner 生于 1978 年,2005年加入苹果,将苹果使用的 GCC 全面转为 LLVM。2010年开始主导开发 Swift 语言。
iOS 开发中 Objective-C 是 Clang / LLVM 来编译的。
Swift 是 Swift / LLVM,其中 Swift 前端会多出 SIL optimizer,它会把 .swift 生成为中间代码 .sil 属于 High-Level IR, 因为 Swift 在编译时就完成了方法绑定,直接通过地址调用属于强类型语言,方法调用不再是像OC那样的消息发送,这样编译就可以获得更多的信息用在后面的后端优化上。
LLVM是一个模块化和可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 编译器,目的是提供惊人的快速编译,比 GCC 快3倍,其中的 clang static analyzer 主要是进行语法分析,语义分析和生成中间代码,当然这个过程会对代码进行检查,出错的和需要警告的会标注出来。LLVM 核心库提供一个优化器,对流行的 CPU 做代码生成支持。lld 是 Clang / LLVM 的内置链接器,clang 必须调用链接器来产生可执行文件。
这里是 Clang 官方详细文档: Welcome to Clang’s documentation! — Clang 4.0 documentation
这篇是对 LLVM 架构的一个概述: The Architecture of Open Source Applications
将编译器之前对于编译的前世今生也是需要了解的,比如回答下这个问题,编译器程序是用什么编译的?看看 《linkers and loaders》这本书就知道了。
LLVM 与 Clang 介绍
LLVM 是 Low Level Virtual Machine 的简称,这个库提供了与编译器相关的支持,能够进行程序语言的编译期优化、链接优化、在线编译优化、代码生成。简而言之,可以作为多种语言编译器的后台来使用。如果这样还比较抽象的话,介绍下 Clang 就知道了:
Clang 是一个 C++ 编写、基于 LLVM、发布于 LLVM BSD 许可证下的 C/C++/Objective C/Objective C++ 编译器,其目标(之一)就是超越 GCC。
Clang 开发事出有因,Wiki 介绍如下:
Apple 使用 LLVM 在不支持全部 OpenGL 特性的 GPU (Intel 低端显卡) 上生成代码 (JIT),令程序仍然能够正常运行。之后 LLVM 与 GCC 的集成过程引发了一些不快,GCC 系统庞大而笨重,而 Apple 大量使用的 Objective-C 在 GCC 中优先级很低。此外 GCC 作为一个纯粹的编译系统,与 IDE 配合很差。加之许可证方面的要求,Apple 无法使用修改版的 GCC 而闭源。于是 Apple 决定从零开始写 C family 的前端,也就是基于 LLVM 的 Clang 了。
Clang 的特性:
- 快:通过编译 OS X 上几乎包含了所有 C 头文件的 carbon.h 的测试,包括预处理 (Preprocess),语法 (lex),解析 (parse),语义分析 (Semantic Analysis),抽象语法树生成 (Abstract Syntax Tree) 的时间,Clang 是 Apple GCC 4.0 的 2.5x 快。(2007-7-25)
- 内存占用小:Clang 内存占用是源码的 130%,Apple GCC 则超过 10x。
- 诊断信息可读性强:我不会排版,推荐去网站观看。其中错误的语法不但有源码提示,还会在错误的调用和相关上下文的下方有~~~~~和^的提示,相比之下 GCC 的提示很天书。
- GCC 兼容性。
- 设计清晰简单,容易理解,易于扩展增强。与代码基础古老的 GCC 相比,学习曲线平缓。
- 基于库的模块化设计,易于 IDE 集成及其他用途的重用。由于历史原因,GCC 是一个单一的可执行程序编译器,其内部完成了从预处理到最后代码生成的全部过程,中间诸多信息都无法被其他程序重用。Clang 将编译过程分成彼此分离的几个阶段,AST 信息可序列化。通过库的支持,程序能够获取到 AST 级别的信息,将大大增强对于代码的操控能力。对于 IDE 而言,代码补全、重构是重要的功能,然而如果没有底层的支持,只使用 tags 分析或是正则表达式匹配是很难达成的。
当然,GCC 也有其优势:
- 支持 JAVA/ADA/FORTRAN
- 当前的 Clang 的 C++ 支持落后于 GCC,参见。(近日 Clang 已经可以自编译,见)
- GCC 支持更多平台
- GCC 更流行,广泛使用,支持完备
- GCC 基于 C,不需要 C++ 编译器即可编译
iOS 开发中用途
一般可以将编程语言分为两种,编译语言和直译式语言。
- 编译语言:像C++,Objective C都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高。
- 直译式语言:像JavaScript,Python都是直译式语言。直译式语言不需要经过编译的过程,而是在执行的时候通过一个中间的解释器将代码解释为CPU可以执行的代码。所以,较编译语言来说,直译式语言效率低一些,但是编写的更灵活,也就是为啥JS大法好。
iOS开发目前的常用语言是:Objective和Swift。二者都是编译语言,换句话说都是需要编译才能执行的。二者的编译都是依赖于Clang + LLVM.
iOS编译
不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端。所以简单的编译过程如图
编译器前端
编译器前端的任务是进行:语法分析,语义分析,生成中间代码(intermediate representation )。在这个过程中,会进行类型检查,如果发现错误或者警告会标注出来在哪一行。
编译器后端
编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。iOS的编译过程,后端的处理如下
- LVVM优化器会进行BitCode的生成,链接期优化等等。
LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码。
执行一次 XCode build 的流程
当你在XCode中,选择build的时候(快捷键command+B),会执行如下过程
- 编译信息写入辅助文件,创建编译后的文件架构(name.app)
- 处理文件打包信息,例如在debug环境下
Entitlements: { "application-identifier" = "app的bundleid"; "aps-environment" = development; }
- 执行CocoaPod编译前脚本(例如对于使用CocoaPod的工程会执行CheckPods Manifest.lock)
- 编译各个.m文件,使用CompileC和clang命令。
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
通过这个编译的命令,我们可以看到
- 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 编译产物