JHHK

欢迎来到我的个人网站
行者常至 为者常成

Swift混编1

-从预编译的角度理解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


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.