Swift在5.0版本之后ABI稳定,也就意味着binary接口稳定,就是说在Swift5及以上编译器编译出来的binary可以运行在Swift5及以上的运行时;在系统层面上可以统一运行环境,不需要在打包时集成多个Swift运行时版本。Swift在5.1同样也是一次大的飞跃,Swift 5.1支持了模块稳定(module stability),这对Swift发展同样意义重大,在Swift 5.1之前的编译出来的Swift二级制库(framework)只能运行在对应的编译链版本下,而module stability则意味着Swift 5.1及以后版本编译的Swift framework可以被Swift 5.1及以后的编译链集成编译,这对Swift二进制库的扩展奠定了基础。
作为Swift framework的开发者,在Swift 5.1版本发布之后,一直想要适配5.1以及发布相应版本的二进制库,下面把Swift module stability适配过程及问题做个记录。
在未做module stability版本兼容之前遇到的问题:在某个版本Swift编译器编译的framework执行运行在相应的Swift编译器版本,如在Xcode10.x(Swift4.2版本)编译的framwork,只能在Xcode10.x中运行。如果在不同版本的编译下运行会抛出如下错误:
Module compiled with Swift 5.1 cannot be imported by the Swift 5.1.2 compiler
前面说了那么多module stability会出现的问题,其实开始module stability很简单,只需要在build setting里面将Build Libraries for Distribution设置为YES即可。
不过,只是将Build Libraries for Distribution并不一定能解决所有问题,如果你在Swift扩展(Extention)中使用了@objc关键字修饰函数,或者实现了UIKit的协议(如在extention中实现了UITableViewDelegate),在编译时将会抛出如下错误:
@objc' class method in extension of subclass of
Class Xrequires iOS 13.0.0
这个错误是苹果在编译时的限制,创建用于分发的框架时,在@objc类的扩展中使用@objc方法需要对Objective-C运行时进行更改,这些更改仅在iOS 13及更高版本中可用。解决方案就是将这些方法移动到类定义本身中,或者@objc关键字(UIKit的协议实现只能定义到类本身中)。
如果使用了Cocoapods依赖了第三方库的话,还要确保依赖的库开启了模块稳定,如果依赖的第三方库没有开启模块稳定设置,可以在Podfile末尾加上如下代码:
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
这里设置所有的第三方库开启BUILD_LIBRARY_FOR_DISTRIBUTION。
即使开启了BUILD_LIBRARY_FOR_DISTRIBUTION,仍然会出现以下错误:
Failed to build module 'MyModule' from its module interface; the compiler that produced it, 'Apple Swift version 5.1.3 effective-4.2 (swiftlang-1100.0.282.1 clang-1100.0.33.15)', may have used features that aren't supported by this compiler, 'Apple Swift version 5.3 effective-4.2 (swiftlang-1200.0.28.1 clang-1200.0.30.1)'
这是由于module stability支持的Swift最低版本是5.1,需要将Swift版本设置为Swift 5。
由于Module stability是向前兼容,而不是向后兼容,所以module stability适配环境:
- Xcode 11及以上版本
- Swift 5.1及以上版本
- iOS 11及以上版本
下面列一下在适配过程中可能遇到的难问题以及解决办法:
| 错误 | 错误类型 | 错误描述 | 解决方案 |
|---|---|---|---|
Redundant conformance of x to NSObjectProtocol | error - 在动态链接时抛出 | 由于这个类已经是NSObject的子类,但是又遵守了NSObjectProtocol协议 | 检查类的协议的一致性,移除冗余的NSObjectProtocol协议。 |
| Use of unimplemented initializer 'init()' for class | error - 在动态链接时抛出 | OC ABI公开的类需要提供公开的init实现 | 提供public修饰的init函数:override public init() |
@objc' class method in extension of subclass of Class X requires iOS 13.0.0 | error | 自iOS 13.0.0以来,OC的互操作性规则发生了变化。而且目前在类扩展中不支持@objc互操作性。Swift论坛上有开放式问题 | 将这些方法移动到类定义本身中,或者@objc关键字(UIKit的协议实现只能定义到类本身中) |
| scoped imports are not yet supported in module interfaces | warning | 通过这个链接了解更多:nshipster.com/import/ | 导入模块而不是特定的声明,例如:更改import class MyModule.MyClass为import MyModule |
| Can’t use framework compiled with Swift 5.2 in Swift 5.1.3 project | error-在链接动态库时抛出 | 由于Module stability是向前兼容,而不是向后兼容 | 升级你的Xcode到Xcode11.4版本集成framework,或者降低你的Xcode版本到Xcode11.3重新打包framework |
| Incompatible module | Error-在链接动态库时抛出 | 为iOS模拟器构建的模块与新的M1共享相同的arch slice。 | 通过指定以下build setting来构建: EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 |
PS: 使用Swift构建SDK踩了太多坑,现在还有一堆坑没有填平,收益远远小于付出,如果再选择一次,绝对不会用Swift来开发SDK。