• iOS 编译一览


    在面试比较常见的一个问题,做iOS这么多年了,能不能讲讲iOS的编译过程?这个过程中都有哪些产物?下面我们就来简单梳理一下

    编译器

    编译的第一步,肯定需要有一个编译器。它是用来完成高级语言到低级语言(机器码)的一个转换。

    • 现代编译器主要工作流程:

    • 现代编译器标准三阶段结构:

      编译器前端(frontend)、优化器、编译器后端(backend)

    iOS开发中我们目前采用的编译器是LLVM,曾经有用GCC(GNU Compiler Collection)。

    LLVM

    LLVM 项目是模块化和可重用的编译器和工具链技术的集合,它的命名最早源自于底层虚拟机器(Low Level Virtual Machine),后面随着项目的发展,LLVM决定放弃缩写的含义,直接打造成了一个品牌。“The name “LLVM” itself is not an acronym; it is the full name of the project.”

    Clang是LLVM的主要子项目之一,它是C系列语言的编译器前端(C、C++、Objective-C、Objective-C++)。

    • Objective-C编译需要Clang与LLVM后端来完成。

    • Swift的编译则是通过Swift编译器前端与LLVM后端完成的。

    Swift的编译器前端中swift和swiftc到底有什么区别呢?
    
    swift 是 Swift 的交互式的编程环境 (REPL)。
    swiftc 是 Swift 编译器。
    swiftc 是 swift 的一个快捷方式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • Swift的编译流程相对于Objective-C来说,中间多了SIL(Swift 中间语言)层,想了解更多的Swift编译过程可以看文末【Swift Compiler】
    LLVM编译流程(Clang为例)

    1、创建一个Command Line Tool项目(OC为例),编译其中的main.m文件

    
    #import 
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSInteger a = 3;
            NSInteger b = 4;
            NSInteger c = MAX(a, b);
            NSLog(@"the result = %ld", c);
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    查看源文件编译所经历的阶段,在终端输入如下命令:

    终端输入:clang -ccc-print-phases main.m
    
    终端输出:
    				+- 0: input, "main.m", objective-c
                +- 1: preprocessor, {0}, objective-c-cpp-output
             +- 2: compiler, {1}, ir
          +- 3: backend, {2}, assembler
       +- 4: assembler, {3}, object
    +- 5: linker, {4}, image
    6: bind-arch, "x86_64", {5}, image
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (1)预处理阶段

    预处理阶段主要做以下事情:

    • 导入头文件
    • 对宏定义进行替换
    • 处理#开头的预编译指令,如:#define、#pragma、#if等

    查看预处理结果:

    终端输入:clang -E main.m
    
    终端输出:
    # 1 "/Path/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
    # 193 "/Path/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
    # 9 "main.m" 2
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSInteger a = 3;
            NSInteger b = 4;
            NSInteger c = ({ __typeof__(a) __a0 = (a); __typeof__(b) __b0 = (b); (__a0 < __b0) ? __b0 : __a0; });
            NSLog(@"the result = %ld", c);
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    通过上面的终端输出结果我们发现:头文件已经被导入且宏定义也已经被成功替换了。

    (2)词法分析阶段

    将预处理完成后的代码转换成token流(有些书中也称token为词法单元),如+、=都可以称之为一个个token。

    终端输入:clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
    
    终端输出:
    ......
    ......
    identifier 'NSInteger'	 [StartOfLine] [LeadingSpace]	Loc=
    identifier 'a'	 [LeadingSpace]	Loc=
    equal '='	 [LeadingSpace]	Loc=
    numeric_constant '3'	 [LeadingSpace]	Loc=
    semi ';'		Loc=
    identifier 'NSInteger'	 [StartOfLine] [LeadingSpace]	Loc=
    identifier 'b'	 [LeadingSpace]	Loc=
    equal '='	 [LeadingSpace]	Loc=
    numeric_constant '4'	 [LeadingSpace]	Loc=
    semi ';'		Loc=
    identifier 'NSInteger'	 [StartOfLine] [LeadingSpace]	Loc=
    identifier 'c'	 [LeadingSpace]	Loc=
    equal '='	 [LeadingSpace]	Loc=
    ......
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (3)语法分析阶段

    验证语法的正确性,将所有节点组合成抽象语法树(AST)

    clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
    
    • 1

    其中校验语法的正确性是通过Static Analysis(静态分析)来完成

    (4)CodeGen生成IR代码(中间代码生成)

    CodeGen负责将语法树从上至下遍历,翻译成LLVM IR代码。LLVM IR即是编译器前端输出,也是编译器后端的输入,属于桥接性质的语言。

    // 生成IR,这一步会产生.ll扩展名文件
    clang -S -fobjc-arc -emit-llvm main.m -o main.ll
    
    // 这块LLVM可以做一些优化工作,Xcode中可以设置优化级别(Optimization Level)
    clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关于Optimization Level相关设置项:

    • None [-O0]:不做优化-默认在Debug模式开启
    • Fast [-O, O1]:优化编译需要更多时间,大型函数需要更多内存。
    • Faster [-O2]:编译器执行几乎所有支持的优化,不涉及空间速度折衷,不执行循环展开、函数内联、寄存器重命名。与Fast设置项比,增加了编译时间和生成代码的性能
    • Fastest [-O3]:开启由Faster设置指定的所有优化项,并打开函数内联和寄存器重命名选项。可能会使可执行文件变大
    • Fastest, Smallest [-Os]:优化大小,开启除了增加代码大小之外的所有更快的优化,执行旨在减少代码大小的进一步优化。Release环境默认开启
    • Fastest, Aggressive Optimizations [-Ofast]:启用Fastest的所有优化,也会启用可能会破坏严格标准合规性激进优化项。
    • Smallest, Aggressive Size Optimizations [-Oz]:通过将重复代码模式隔离到编译器生成的函数中来进一步节省大小。

    更详细的解释,请参照文末“Build Settings Reference”

    如果开启bitcode,则会进一步优化生成中间码(Xcode 14不推荐使用bitcode)

    // 产生.bc扩展名文件
    clang -emit-llvm -c main.m -o main.bc
    
    • 1
    • 2

    (5)生成汇编代码

    // 产生.s扩展名文件
    clang -S -fobjc-arc main.m -o main.s
    
    • 1
    • 2

    (6)生成目标文件

    // 产生.o扩展名文件
    clang -fmodules -c main.m -o main.o
    
    • 1
    • 2

    (7)生成可执行文件Mach-O(链接)

    // Mach-O文件:
    clang main.m -o main
    
    • 1
    • 2

    测试运行可执行文件:

    // 当前文件路径下,执行Mach-O文件
    ./main
    
    • 1
    • 2

    编译流程示意图:

    Xcode Build 全过程(Xcode 12.3)

    关于编译速度优化思路.

    到目前为止,整个iOS的编译流程基本上已经梳理清楚。

    那么整个编译过程中都产生了哪些文件呢?你心中应该已有答案!

    参考资料

    完整优秀版请移步小专栏:
    iOS 编译一览

    更多好文推荐,扫描下方的二维码,关注《iOS开发秘籍》公众号,点关注不迷路!
    在这里插入图片描述

    本文内容中部分来自网络,后续会持续更新完善。欢迎一起学习交流!

    如需转载,请注明出处

    iOS 编译一览

  • 相关阅读:
    vue组件
    【优雅的参数验证@Validated】@Validated参数校验的使用及注解详解——你还在用if做条件验证?
    VSCode 工具常用插件
    【B3637 最长上升子序列】
    2022SDNU-ACM结训赛题解
    某讯D-Link AC集中管理平台未授权访问漏洞复现 CNVD-2023-19479
    kindle通过原装数据线连接mac book不显示设备解决办法
    对象实例、类信息、常量、静态变量分别在运行时数据区的哪个位置?
    dubbo项目整合nacos注册中心问题记录
    search_engine:搜索引擎实现
  • 原文地址:https://blog.csdn.net/yangshebing21/article/details/126129842