-从预编译的角度理解Swift与Objective-C及混编机制
目录
预编译知识指北
#include 是对头文件的简单复制
#import 也是对头文件的复制,但保证不会重复粘贴
在预编译阶段会对#import引入的头文件进行替换(递归替换)
这种引入方式存在的问题:
最主要的问题是:
会被重复复制和重复编译
另外还有:
一、健壮性
比如定义了宏定义 #define readonly 0x01,会导致编译器报错
二、拓展性
在其它文件都要引用的文件中添加了一个 iAd.h 文件的引用,这意味着其他文件也会把 iAd.h里包含的东西纳入进来
M个源文件,每个文件引入N个头文件,那么编译他们的时间就是M*N
PCH(PreCompiled Header)是一把双刃剑
将pch里的内容进行预编译,变成中间格式的二进制代码,在.m文件编译时直接读取,无需再次编译
这样pch文件内引用的头文件只需要编译一次不会被重复编译
存在的问题:
PCH引入的头文件会出现在代码的任何地方,造成冗余
Clang Module 的来临!
一、有什么好处
1、只会编译一次,避免重复编译
2、按需引入,不会像pch那样的全量引入
3、独立空间,不会被上下文篡改,比如前面提到的 #define readonly 0x01
二、modulemap在哪里使用
OC -> xx-swift.h -> xx.swiftmodule -> swift代码
swift -> module.modulemap -> oc代码
As Bridging-Header can help us in App Target and App Test Target, not in static library or dynamic
libraries to use the Objective C / C APIs into Swift classes, modulemap can help us here.
在framework中是不支持使用Bridging-Header.h文件的会报错
using bridging headers with framework targets is unsupported。
那么framework中的Swift代码如何使用oc代码,通过modulemap来使用
app target 中的Swift代码如何使用oc代码,通过 bridging headers 或者 modulemap来使用
作者:season_zhu
链接:https://juejin.cn/post/7139724115157450765
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
提示:
lxy:
1、modulemap可以给swift使用,也可以给oc代码使用
2、modulemap在framework的静态库和动态库中可以生成和指定,app target 和 .a库无法生成
3、Defines Module在app target下开启并没有反应,也不知道生成的modulemap文件在哪里
4、在app target下配置自定义modulemap文件,在工程中的.m文件通过module的方式引用也不生效
三、modulemap文件介绍
-参考文章:理解 Clang Module 和 Module Map 语法
modulemap文件
module LCCat {
header "Cat.h" // 导出Cat.h头文件
export * // 导出Cat.h文件内引用的所有头文件
// 子模块 Sub
module Sub {
header "sub.h"
export *
}
//使用时只能显示导入:@import LCCat.InternalModule
explicit module InternalModule {
header "InternalHeader.h"
export *
}
}
framework module SwiftFramwork 定义了一个 framework 语义的模块
umbrella header “SwiftFramwork.h” 说明把 SwiftFramwork.h 文件作为模块的 unbrella header,伞头文件相当于模块中所有公共头文件的一个集合,方便使用者导入。
export * 将所有子模块中的符号进行重导出到主模块中
module * { export * } 定义子模块,这里为 * 则是为 umbrella header 中的每个头文件都创建一个子模块。
// framework 代表这是一个库
framework module SwiftFramwork {
//umbrella 伞文件,统一管理头文件
//header 去framework下的header目录里查找
umbrella header "SwiftFramwork.h"
export * // 导出伞文件内引用的所有头文件
//为伞文件内的所有头文件创建一个子模块
module * { export * }
}
GPT中说明:
modulemap 文件在编译阶段发挥作用,当编译器处理源代码时,会使用 modulemap 文件来确定模块化的头文件和代码结构。
四、编译选项介绍
1、要想在Framework中生成SwiftFramwork-swift.h文件供OC代码使用需要打开:
打开之后会在modulemap文件内自动生成 SwiftFramwork.Swift 子模块
framework module SwiftFramwork {
umbrella header "SwiftFramwork.h"
export *
module * { export * }
}
module SwiftFramwork.Swift {
header "SwiftFramwork-Swift.h"
requires objc // 表示该模块仅在 Objective-C 环境中可用
}
2、为什么我从来没看到过 @import 的写法呢?
这是因为 Xcode 的编译器能够将符合某种格式的 #import 语句自动转换成 Module 识别的 @import 语句,从而避免了开发者的手动修改。
唯一需要开发者完成的就是开启相关的编译选项。
Enable Module 选项是指引用系统库的的时候,是否采用 Module 的形式
Defines Module 是指开发者编写的组件是否采用 Module 的形式开启后会生成 *.modulemap文件
3、使用自定义的.modulemap文件
注意是相对路径
头文件的搜索方式
一、header search path
Header Search Path:不会有任何限制,它普适于任何方式的头文件引用
System Header Search Path:是针对系统头文件的设置,通常代指 <> 方式引入的文件
User Header Search Path:是针对非系统头文件的设置,通常代指 “” 方式引入的文件
二、header map
Header Map:它的核心功能就是让编译器能够找到相应头文件的位置。
一旦开启 Use Header Map 选项后,Xcode 会优先去 hmap 映射表里寻找头文件的路径,只有在找不到的情况下,才会去 Header Search Path 中提供的路径遍历搜索。
lxy:
在创建的demo工程中,如果关闭了 Use Header Map并且没有配置 header search path的情况下,会报找不到头文件的错误
GPT中说明:
1、hmap 文件在编译阶段的头文件查找过程中发挥作用。编译器在处理每一个源文件时,都需要找到并引入相关的头文件。hmap 文件加速了这个查找过程,减少了编译器在文件系统中搜索头文件的时间。
2、hmap 文件主要通过构建系统(如Xcode)自动生成,并用于优化编译过程中的性能。它将头文件的逻辑路径(例如 #include “MyHeader.h”)映射到实际的文件路径,避免了不必要的文件系统遍历
3、总结:hmap:也在编译阶段起作用,专注于加速头文件查找过程,是一种编译器优化手段,用于减少头文件查找时间,提高编译速度
三、framework中的头文件搜索
1、查找系统库头文件
当我们查找 Foundation/Foundation.h 这个文件的时候,我们会首先判断是否存在 Foundation 这个 Framework。
$SDKROOT/System/Library/Frameworks/Foundation.framework
接着,我们会进入 Framework 的 Headers 文件夹里寻找对应的头文件。
$SDKROOT/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h
如果没有找到对应的文件,索引过程会在此中断,并结束查找。
2、自定义库头文件 Framework Search Path
还有一种头文件搜索机制,它是基于 Framework 这种文件结构进行的
对于开发者自己的 Framework,Clang 在检查 Headers 目录后,会去 PrivateHeaders(如果有) 目录中寻找是否存在匹配的头文件,如果这两个目录都没有,才会结束查找。
注意下文章中的:揭开 Public、Private、Project 的真实面目
互相调用总结
oc代码
1、使用oc代码(头文件、modulemap)
2、使用swift代码(ProjectName-swift.h文件、modulemap可以也是因为子模块包含了ProjectName-swift.h文件)
ProjectName-swift.h头文件没有被放到目录时,可以在下面存放中间产物的目录内找到
/Users/YourUsername/Library/Developer/Xcode/DerivedData/YourProjectName-*/Build/Intermediates.noindex/YourLibraryName.build/Debug-iphoneos/YourLibraryName.build/Objects-normal/arm64/YourLibraryName-Swift.h
swift代码
1、使用oc代码(需要把oc头文件放在swift代码所在 target 的 bridging-header.h文件内)
2、使用oc代码(framework内不允许使用bridging-header.h,将oc头文件放在modulemap的伞文件内)
3、使用swift代码(swiftmodule,不管是静态动态framework还是.a库只要包含swift代码都会有swiftmodule文件生成)
XCFramework内三个文件
.swiftmodule:
包含序列化过的 AST(抽象语法树,Abstract Syntax Tree)
也包含 SIL (Swift 中间语言,Swift Intermediate Language)
.swiftdoc:
用户文档
.swiftinterface:
Module stability
行者常至,为者常成!