浅谈iOS工程编译

编程语言一般可以分为两类:编译型语言和直译式语言。在iOS里面Objective-C、Swift都是编译型语言,也就是说需要通过编译器生产机器码,然后才能执行。也就需要依赖编译器去做编译,编译器一般又可以分为前端编译器和后端编译器。Objective-C是以clang作为前端编译器、而swift是以swift作为前端编译器,他们两者都是以LLVM作为后端编译器。

编译基础理论

编译大致可分为:预处理、词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成等步骤。词法分析、语法分析、语义分析,三个步骤为编译器的前端,后三个步骤为编译器的后端。

预处理

预处理的做的一些事情:

  • 头文件替换
  • 宏展开
  • 注释清除
  • 预编译指令处理
  • 条件编译
  • ……等事情
编译器前端
词法分析

一般来说,源码编译的第一个步骤叫做词法分析,像我们阅读英语文章一样,会一个个字母去扫描,以单词为单位去读取。这里的单词,在编译里叫做"词法记号",也就是token,词法分析的过程就是生成一个个token的过程。在生成这些token的过程中会遇到很多问题,比如:如何去区分int、double、void、这些语言关键字、如何去识别 if(a>10) 这种没空格的语句等问题。为了解决这些问题,我们会引入“正则文法”、“有限自动机”两个概念。
正则文法 就是我们对token的生成会有一些规则,而这些规则我用正则文法来表达,符合正则文法的表达式,称之为“正则表达式”。
有限自动机 就是有限个状态的自动机器,它可以在多个状态中任意切换,每一次切换都代表不同的意义。比如我们现在定义:> 状态为 GreaterThan、>= 状态为GreaterEqual,那么我们在识别 >= 这个token的时候,我们就会进行有限自动机的状态切换,首先识别到 > 的时候 text = “>”,status = GreaterThan, 继续往后读取,text = “>=”,status = GreaterEqual。 status代表不同的状态,可以是一个枚举值。
词法分析就是这样会对语句扫描并做分词,分词可以看做对语句按照某些规则进行分割,生成一个未识别的token,然后我们对token进行识别。

语法分析

编译器下一个阶段的工作是语法分析。词法分析是识别一个个的单词(token),而语法分析就是在词法分析的基础上识别出程序的语法结构。这个结构是一个树状结构,是计算机容易理解和执行的。这个树状结构我我们叫做抽象语法树(Abstract Syntax Tree,AST)。
在计算机的世界里总是一个结构套着另一个结构,大的程序套着子程序,子程序又可以包含子程序。在抽象语法树这个结构里,每一个节点(子树)都是一个语法单元,这个单元的构成规则就叫“语法”。每个节点还可以有下级节点。比如我们需要计算一个表达式:“1+2*3”,我们可以得到如下图这样的一课树结构的AST。
在这里插入图片描述

形成AST的好处是什么呢?我们知道计算机世界里有很多数据结构,而这些数据结构就是正好是计算机容易处理的,比如上图这棵树,我只需要从根节点开始遍历这棵树就可以得到结果了。
语法分析的过程就是生成AST的过程,然后编译器可以基于AST做下面的语义分析。

语义分析

语义分析,顾名思义就是分析其语句的意识。计算机语言的语义一般都需要遵循一些规则,而编译器进行语义分析的时候正是检查这些语句是否适合规则,比如:

  • 某个表达式的计算结果是什么数据类型?如果有数据类型不匹配的情况,是否要做自动转换?
  • 如果在一个代码块的内部和外部有相同名称的变量,我在执行的时候到底用哪个? 就像“我喜欢又聪明又勇敢的你”中的“你”,到底指的是谁,需要明确。
  • 在同一个作用域内,不允许有两个名称相同的变量,这是唯一性检查。你不能刚声明一个变量 a,紧接着又声明同样名称的一个变量 a,这就不允许了。
  • …………
    语义分析基本上就说明确以上的问题,根据语义规则来分析判断。
    语义分析的结果会标注在抽象语法树的节点上。每个节点所包含的信息有很多,比如:源码行数(报错时定位等)。
    将这些信息标注以后,编译器就可以在后面根据这些信息生成目标代码了。
编译器后端
中间代码生成

所谓“中间代码”是一种结构简单、含义明确的记号系统,这种记号系统可以设计为多种多样的形式,设计的时候会重点考虑两点:

  • 容易生成
  • 容易被翻译成目标代码
    中间代码也是一种编译器内部的表示形式,很多编译程序都采用了一种近似“三地址指令”的“四元式”中间代码,这种四元式形式为:
    (运算符,运算对象1,运算对象2,结果)
代码优化

代码优化这一过程主要做的事情是 对上一过程生成的中间代码进优化,目的是为了在下一阶段生成的目标代码更加高效。

目标代码生成

这一阶段的任务是把中间代码变换成特定机器上的绝对指令代码或可重定位的指令代码或汇编指令代码。这是编译的最后阶段,它的工作与硬件系统结构和指令含义有关,这个阶段的工作很复杂,涉及硬件系统功能部件的运用、机器指令的选择、各种数据类型变量的存储空间分配以及寄存器和后缓寄存器的调度等。

iOS工程编译

Objective-C工程采用的是Clang+LLVM编译,LLVM采用的是三相设计,三相设计很好地解决了编译型语言适配不同架构的问题,他通过LLVM Optimizer 进行中转,如果我们需要对arm或者x86或者其他进行适配,那么只需要提供两种后端编译器去适配两种架构。
iOS编译则由前端Clang负责解析,验证和诊断输入代码中的错误,然后将解析的代码转换为LLVM IR,后端LLVM编译把IR通过一系列改进代码的分析和优化过程提供,然后被发送到代码生成器以生成本机机器代码。
简单的编译流程如下:
在这里插入图片描述

iOS工程编译过程
  • 写入辅助文件:将项目的文件结构对应表、将要执行的脚本、项目依赖库的文件结构对应表写成文件,方便后面使用;并且创建一个 .app 包,后面编译后的文件都会被放入包中;
  • 运行预设脚本:Cocoapods 会预设一些脚本,当然你也可以自己预设一些脚本来运行。这些脚本都在 Build Phases 中可以看到;
  • 编译文件:针对每一个文件进行编译,生成可执行文件 Mach-O,这过程 LLVM 的完整流程,前端、优化器、后端;
  • 链接文件:将项目中的多个可执行文件合并成一个文件;
  • 拷贝资源文件:将项目中的资源文件拷贝到目标包;
  • 编译 storyboard 文件:storyboard 文件也是会被编译的;
  • 链接 storyboard 文件:将编译后的 storyboard 文件链接成一个文件;
  • 编译 Asset 文件:我们的图片如果使用 Assets.xcassets 来管理图片,那么这些图片将会被编译成机器码,除了 icon 和 launchImage;
  • 运行 Cocoapods 脚本:将在编译项目之前已经编译好的依赖库和相关资源拷贝到包中。
  • 生成 .app 包
  • 拷贝 Swift 标准库
  • 对包进行签名
  • 打包
新建的工程编译日志

在这里插入图片描述

可以看到这个新建的工程是按照上面的编译来的,不过步骤可能不一样,这个过程我们可以在Build Phases中指定,我们不仅可以指定编译步骤和顺序,我们还可以指定编译规则(Build Rules)等。

总之

编译过程是一个非常复杂的过程,本文只是简单的描述了编译相关的过程,也是是为了在iOS开发中能够更加得心应手。

原理是我们构建整个代码世界的基础,不懂原理都只能让我们做一个垒砖的,而不是整个工程的控制者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值