动态库中中使用modulemap使OC与Swift互相调用
动态库中使用modulemap使OC与Swift互相调用
# 动态库中OC如何与Swift互相调用
在RFramework使用过程中,遇到了一个难以复现的问题,再所以需要对SDK添加Log进行监控,经过调研,决定引入`CocoaLumberjack`这个日志库,但因为SDK本身有一些Swift的代码,现在加入这个日志库后,需要Swift中的代码调用这个日志库了,所以
### 1. **ObjC 调用 Swift**
在Framework中实现 ObjC 调用 Swift,和平时在项目中那样,过设置`Swift Compiler -> Objective-C Generated Interface Header Name`就可以了
- 在 Build Setting 中,搜索SWIFT_OBJC_INTERFACE_HEADER_NAME,配置正确的名字,比如默认的`$(SWIFT_MODULE_NAME)-Swift.h`
- 然后在 ObjC 中引入该模块的 Swift 头文件,然后使用#import <XXX/XXX-Swift.h>倒入生成的头文件
- 若在 ObjC的 .h 中引入,则可以通过向前声明的方式,@class XXX
```ObjC
// RLog.m
#import <RFramework/RFramework-Swift.h>
-(void)log:(NSString *)message {
DDDebug(message);
}
-(void)useSwiftTest {
RPreviewController *preview = [[RPreviewController alloc] init];
[preview show];
}
2. Swift 调用 ObjC
但是在Framework中实现 Swift 调用 ObjC,通过设置 Bridging-Header 的方式是无法解决的。如果尝试在Swift Compiler -> Objective-C Bridging Header填入OC的头文件,然后编译,最终将会报错: Using bridging headers with framework targets is unsupported
此时可以使用 modulemap 的方式访问,创建一个RFramework.modulemap文件
/* RtFramework.modulemap */
framework module RFramework {
umbrella "Headers"
export *
}
然后在在 buildSettings → packaging → Module Map File 写入这个 modulemap文件的路径,然后把想要在Swift中使用的OC文件,比如Pikachu.h放在Build Phases → Hearders → Public 中
// RPreviewController.swift
@objcMembers public class RPreviewController: UIViewController {
...
func show() {
....
webView.loadRequest(request)
RLog.log("RPreviewController.show")
}
}
此时就可以在动态库内部,使用Swift调用OC了.
那这个 modulemap 是个啥意思呢,它是用来描述头文件与module之间映射的关系,比如前边写的那个文件: framework module 代表这个是一个Framework的module, Framework的名称叫做RFramework
umbrella "Headers",表示该模块包含 "Headers"目录下的所有头文件.其实从名字看就也能大概理解,伞状头文件,伞下边的所有东西(伞兵啊什么的…),都属于伞的一部分
OK,那么Headers文件夹是哪里来的呢?当framework编译完后,编译产物会自动生成一个Headers文件夹,并且把所有 Public 的头文件都放在这个文件夹里面 
在外部项目中使用:
// ViewController.m
#import <RFramework/RFramework.h>
//或者使用module语法 `@import RFramework;`
- (void)viewDidLoad {
[super viewDidLoad];
RPreviewController *m = [[RPreviewController alloc] init];
RLog *l = [[RLog alloc] init];
}
当然在看别人第三方库的时候,会发现还有另外一种用法:
framework module RFramework {
umbrella header "RFramework.h"
export *
}
这种写法的意思是,只有在"RFramework.h" 中引用的头文件,才能作为该模块的一部分给外部使用,比如我们在 RFramework.h 中导入下边的文件
// RFramework.h
#import "RPreviewController.h"
此时,你会发现 RPreviewController 可以正常使用, RLog报错了,使用未声明的文件,即使你强行导入这个文件 #import <RFramework/RLog.h> 也还是报错
#import <RFramework/RFramework.h>
- (void)viewDidLoad {
[super viewDidLoad];
RPreviewController *m = [[RPreviewController alloc] init];
RLog *r = [[RLog alloc] init]; // Use of undeclared identifier 'RLog'
}
子module的语法,把开始的例子的modulemap文件做些修改,在module中再声明两module: Preview 和 Log
framework module RFramework {
module Preview {
header "RPreviewController.h"
}
module Log {
header "RLog.h"
}
}
使用的时候只导入 #import <RFramework/RPreviewController.h> 或者使用module语法 @import RFramework.Preview;就可以了
- (void)viewDidLoad {
[super viewDidLoad];
RPreviewController *p = [[RPreviewController alloc] init];
}
export * 首先说 export,也可以叫做re_export
使用上边的那个modulemap文件,然后只导入@import RFramework.Preview;,这时编译就会报错,要想使用RLog的话,需要导入模块 Log
- (void)viewDidLoad {
[super viewDidLoad];
RPreviewController *p = [[RPreviewController alloc] init];
// Declaration of 'RLog' must be imported from module
//'RFramework.Log' before it is required
RLog *l = [[RLog alloc] init];
}
当然,直接导入Log模块就不会报错了,但是如果我既不想导入,还想正常使用该怎么办呢?
module Preview {
header "RPreviewController.h"
export Log
}
对modulemap文件稍做修改,把Log 在 Preview 的模块中重新导出去,使用就不会报错,所以 上边的 export * 的意思就很明显了,导出当前 头文件/头文件集合 的所有的符号
当然,subModule其实在swift中,只有象征意义了,尽管只倒入
参考1: 京东App Swift 混编及组件化落地 - 知乎 (zhihu.com)
参考2: Swift与Objective-C互相调用(总结) - 掘金 (juejin.cn)
本博客文章采用 CC 4.0 协议,转载需注明出处和作者。