一、从iOS中#import 、@import、#include说起
#include
就是将目标.h文件中的内容拷贝到当前文件中,并替换掉这句#include
。这样做可能会因为重复引用带来编译错误,比如B和C都引用了A,D又同时引用了B和C,这样D引用了A两次。为了解决这个问题,OC加入了
#import
,使得头文件只被引用一次。其原理是通过下面方式来实现的。
#ifndef xxx
#define xxx
问题来了:当引用关系很复杂时,编译引用所占的代码量就会大幅上升,因为被引用的头文件在引用的地方都被拷贝了一次。
为了解决这个问题,C语言引入了预编译头文件(
PreCompiled Header
)问题又来了。。。理论上说,想要提高编译速度,可以把所有头文件引用都放到
pch
文件中。但是这样面临的问题是在工程中随处都能访问可能不该访问的东西,这样的结果显然不是很理想的。于是苹果创造了
Modules
来解决这个问题,而使用的方式就是@import
。
Modules
会在实际编译时加入了一个用来存放已编译添加过的Modules
列表。首先在Modules
列表内查找,如果在编译的文件中引用到某个Module
,则直接使用;如果没有,则把引用的头文件编译后加入到这个表中。这样被引用到的Modules
只会被编译一次,也避免了在工程中随处都能访问可能不该访问的东西。
#import 、@import、#include的区别
二、import""还是import<>
-
import""
双引号用于本地的头文件,需要指定相对路径; -
import<>
尖括号是全局的引用,其路径由编译器提供,如引用系统的库。
https://www.jianshu.com/p/3fafcb0dc37f
三、Modules是什么❓
@import WebKit.WebKitLegacy; //in Objective-C
import WebKit.WebKitLegacy //in Swift
可以看到Objective-C
和 Swift
都非常好地支持了 Modules import
,你可以非常清晰地引入 API 声明。
当你使用Modules
引入时,预处理器并不会像 “#include”那样使用 M*N 量级的重复拷贝粘贴。而是巧妙地通过一个列表来存放已经编译处理过的 Modules
列表,而声明的引入会首先在这个表内查找,如果没有找到会去编译添加进来。所以 Modules
的引入只会被处理一次,可以解决前面提到的引用泛滥问题。
modules
和 headers
通过一个 map
来进行一种关系映射,这个 map
文件就叫做 modulemap
. 这个文件从语义上描述了你的函数库物理结构。
那么实际在编译过程中Modules
到底代表着什么呢?其实 Modules
是一种预编译技术,当一个模块被导入时,编译器在处理它时会生成一个新的子进程(非 fork),这个子进程拥有干净的context
来编译这个 module
(这样就不会产生命名空间冲突等干扰),然后 module
的编译结果会被持久化到这个模块的二进制缓存中,那么下次引用编译的时候就会非常快。 modules
由头文件映射而成,所以当这些头文件改动时,module
还会自动重新编译刷新缓存,不需要我们主动干预。
四、Modules 和 Swift
如果仅仅只是为了提升头文件预处理速度还没必要这么大费周章地搞 Modules
这个东西,我的猜测是 Swift
这个项目开始设计时便考虑了和 C/C++/ObjC
的交互问题,使用 Modules
便可以方便桥接了。
Swift
的 Modules
和 我们上面讲的稍微有点不一样,它并不存在 modulemap
这个东西,而是直接编译生成的一个.swiftmodule
文件。Apple 官方对于 Swift
的模块系统也有一点解释,就是说 Xcode 中的每一个 target 都对应着一个 Swift Module
。
我们前面提到 modulemap
最终预编译后产生的是一个二进制的缓存,Swift Modules
也一样,.swiftmodule
文件里面存放的就是一些序列化后的 AST
(可能还有些 SIL
)。因为 Swift
并没有头文件引入机制,所以Swift
和 C/C++/ObjC
交互时,通过这种 Modules
机制,从二进制层面上交互会非常便捷. 最终进行编译链接便能确定互相调用函数或对象的相对或绝对地址和内存布局了。