用swift开发framework时采用OC混编的解决方案
随着swift ABI的稳定,越来越多的开发者开始使用swift语言开发项目,但是由于大部分工具库也还是使用OC写的,因此我们不得不需要在项目中采用swift与oc混编。
在开发app项目时,swift与oc混编其实很容易,xcode会自动为我们建立一个桥接文件,这样我们就很容易的在swift中调用oc的方法或类对象。
但是在开发framework的时候,xcode不会为我们建立这个桥接文件,因此我们在framework中也就不能采用桥接的方式进行混编了。
framework中实现混编的方式有两种:
第一种:也是最简单的一种,就是将oc的头文件暴露出来,并在framework的头文件中导入。(缺点:我们不想对外暴露的类或方法,不得不暴露出去)
这样一来,我们就可以在swift中直接引用oc的类了
第二种:配置modulemap,这个方法可以避免我们不得不对外暴露我们的oc类。
(1):在项目中创建module.modulemap文件,具体内容如下:
module MyObjcFramework { header "oc/MyObjc.h" // 这里的路径是相对于module.modulemap的所在目录 export * }
这里我们定义了一个MyObjcFramework的模块,也就是提供给swift导入的模块名称。
header是该模块的头文件,如果你需要了解关于module的更多详细信息,可以查看这篇文章,或者Clang Module's documentation。
(2):配置Swift Compiler - Search Paths Import Paths:
这里是为了告诉编译器,我们自定义的module所在路径,这里指定了项目的根目录:$(SRCROOT),当然你也可以指定的更具体,像下面这样:
然后我们就可以在swift中导入我们自定义的module,从而引用oc类。
import Foundation import MyObjcFramework // 导入自定义模块 public func testPrint(_ str: String) { let objc = MyObjc() objc.printStr(str) }
以上两种方法解决了我们在使用swift开发framework时与oc混编的问题,一般常用第二种方式,不过依具体情况决定。
另外说一下swift开发framework与OC的区别,使用swift开发framework导出的framework文件中的Modules目录下多出一个xxx.swiftmodule包,这个是swift提供的公共组件接口定义。
我们看一下这个包里都有哪些文件:
swiftdoc应该是api文档的描述。swiftmodule定义了MyKit组件的api公共接口。
Project里的文件如下:
另外当你在Build Settings中设置Build Libraries for Distribution = YES时,再次Build你的framework后,在xxx.swiftmodule包中会多出一个swiftinterface文件。
这个是swift5.1之后对swiftmodule的补充,它们的目的都是为了实现编译一个向后兼容的二进制库。
下面是重点,重点,重点。
在合并模拟器与真机的二进制库时,不能像OC那样简单的将合并后的二进制文件替换就可以了,因为swift中模块定义的公共接口都是在swiftmodule中定义的,而它们也是区分架构的,因此你需要将合并后的二进制文件替换之外,将不同架构的swiftmodule和swiftinterface文件拷贝到当前framework/Modules/xxx.swiftmodule包中。
当然还有另外一种合并多平台架构的方式,那就是xcframework,但是我却在这里遇到了一个问题,那就是在swift于OC混编时使用modulemap时生成的xcframework,导入到实际app工程时报错,下面是具体的错误:
MyHandler就是在swift编写的framework工程中利用module.modulemap对OC文件的一种引入方式,对应的modulemap如下:
module "MyHandler" { umbrella header "./src/oc/MyTextHandler.h" export * module * { export * } }
使用MyHandler的地方:
import UIKit import DrFlexLayout_swift import MyHandler public class MViewController: UIViewController { let text: String let handler: MyTextHandler public init(text: String) { self.text = text self.handler = MyTextHandler(prefix: "T") super.init(nibName: nil, bundle: nil) } }
framework的工程可以正常编译通过,但是在合并成xcframework后,将xcframework导入到app工程后编译就会包上面的错误。
这个问题我暂时没办法解决,希望知道的解决方法朋友在下面留言告诉我一下,不胜感激!
难道就不能使用xcframework了吗?当然是可以使用了,前面我们提到过在swift编写的framework中混编OC或c时,还有另一种方法,那就是将OC或c的头文件暴露在framework工程的头文件中,这样生成的xcframework就可以正常使用了,但是缺点自然是有的,这也是没办法的办法。
感谢@ronzheng的帮助,以上的问题已经得到解决。解决办法是将import MyHandler替换成@_implementationOnly import MyHandler即可。我遇到的这个问题也有其他人同样遇到了,可以看这里查看,关于@_implementationOnly的作用没有官方解释,不过我推测就是将被修饰的导入的模块私有化,在使用中我同样遇到了一些其他问题,在评论中有提到。因此在使用@_implementationOnly导入内部私有模块时,最好放在内部类中使用。