Swift Module Stability探索

3,629 阅读4分钟

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 X requires 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 NSObjectProtocolerror - 在动态链接时抛出由于这个类已经是NSObject的子类,但是又遵守了NSObjectProtocol协议检查类的协议的一致性,移除冗余的NSObjectProtocol协议。
Use of unimplemented initializer 'init()' for classerror - 在动态链接时抛出OC ABI公开的类需要提供公开的init实现提供public修饰的init函数:override public init()
@objc' class method in extension of subclass of Class X requires iOS 13.0.0error自iOS 13.0.0以来,OC的互操作性规则发生了变化。而且目前在类扩展中不支持@objc互操作性。Swift论坛上有开放式问题将这些方法移动到类定义本身中,或者@objc关键字(UIKit的协议实现只能定义到类本身中)
scoped imports are not yet supported in module interfaceswarning通过这个链接了解更多:nshipster.com/import/导入模块而不是特定的声明,例如:更改import class MyModule.MyClassimport MyModule
Can’t use framework compiled with Swift 5.2 in Swift 5.1.3 projecterror-在链接动态库时抛出由于Module stability是向前兼容,而不是向后兼容升级你的Xcode到Xcode11.4版本集成framework,或者降低你的Xcode版本到Xcode11.3重新打包framework
Incompatible moduleError-在链接动态库时抛出为iOS模拟器构建的模块与新的M1共享相同的arch slice。通过指定以下build setting来构建: EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64

PS: 使用Swift构建SDK踩了太多坑,现在还有一堆坑没有填平,收益远远小于付出,如果再选择一次,绝对不会用Swift来开发SDK。