桥接参考
Skip 的文档介绍了已编译 Swift 与转译后的 Swift,以及 Kotlin/Java 之间的桥接技术。本参考页详细列出哪些 Swift 语言特性和类型可以被桥接。除下文特别说明的情况外,桥接可以双向进行(Swift 到 Kotlin/Java,以及 Kotlin/Java 到 Swift),两个方向具有相同能力。
下表详细说明 Skip 对各种 Swift 语言特性的桥接支持。✓ 表示完全支持或支持度很高,~ 表示部分支持,✕ 表示不支持或支持度很低。未来版本可能会补齐一些尚未支持的特性,但另一些限制源自 Swift 与 Kotlin 语言及运行时之间的深层不兼容。
- ✓ 类
- ✓ 继承,最多 4 层
- ✓ 结构体,参见下文可变结构体
- ✓ 构造器合成
- ✓
Equatable合成 - ✓
Hashable合成
- ✓ 协议
- ✓ 继承
- ✓ 属性要求
- ✓ 函数要求
- ✕ 构造器要求
- ✕ 静态要求
- ✓ 枚举
- ✓ 不带关联值的枚举
- ✓ 带关联值的枚举
- ✕ 可变属性和函数
- ✓ 嵌套类型
- ~ 扩展
- ✓ 具体类型扩展
- ~ 协议扩展
- ✓ 扩展当前模块中的类型
- ~ 扩展其他模块中的类型(仅 Kotlin 到 Swift)
- ~ 泛型类型,参见下文泛型
- ✓ 元组,最多 5 个元素
- ✕ 不支持作为集合元素或闭包参数
- ✓ 类型别名
- ✓ 属性
- ✓ 全局属性
- ✓ 成员属性
- ✓
let - ✓
var - ✓ 静态属性
- ✓ 存储属性
- ✓ 计算属性
- ✓ 可抛出错误的属性
- ✕ 延迟属性
- ✓ 函数
- ✓ 全局函数
- ✓ 成员函数
- ✓ 按类型重载
- ✓ 按参数标签重载
- ✕ 按返回类型重载
- ✓ 静态函数
- ~ 泛型函数,参见下文泛型
- ✓ 可抛出错误的函数
- ✓ 默认参数值
- ✕
inout参数 - ✕ 可变参数
- ✕
@autoclosure参数 - ✕ 参数包
- ✓ 构造器
- ✕ 可失败构造器
- ✓ 析构器
- ✓ 闭包,最多 5 个参数
- ✕ 不支持作为集合元素或其他闭包的参数
- ✓ 错误,参见下文错误
- ~ 并发
- ✓ 异步函数
- ✓ 异步属性
- ✓ 异步闭包
- ✓
@MainActor - ✓ 自定义 actor
- 不支持非
private可变属性;请通过函数修改状态
- 不支持非
- ~ 运算符
- ✓ 通过
==自定义Equatable - ✓ 通过
hash(into:)自定义Hashable - ✓ 通过
<自定义Comparable - ✕ 自定义下标运算符
- ✕
callAsFunction支持 - ✕ 其他自定义运算符
- ✓ 通过
- ✕ 键路径
下表详细说明 Skip 对 Swift 标准库内建类型的桥接支持。
- ✓
Any - ✓
AnyHashable - ✓
AnyObject - ✓
Bool - ✕
Character - ✓ 数值类型
- ~
Int和UInt在 JVM 上为 32 位
- ~
- ✓
String - ✓ 可选类型
- ✕ 复合类型(例如
A & B) - ✓ 完全限定的 Kotlin/Java 类型,转换为
AnyDynamicObject - ✓
Array,在kotlincompat模式下转换为kotlin.collections.List - ✓
AsyncStream,在kotlincompat模式下转换为kotlinx.coroutines.flow.Flow - ✓
AsyncThrowingStream<*, Error>,在kotlincompat模式下转换为kotlinx.coroutines.flow.Flow - ✓
Data,在kotlincompat模式下转换为[byte] - ✓
Date,在kotlincompat模式下转换为java.util.Date - ✓
Dictionary,在kotlincompat模式下转换为kotlin.collections.Map - ✓
Error,在kotlincompat模式下转换为kotlin.Throwable - ✓
NSNumber,转换为java.lang.Number - ✕
OptionSet - ✓
Result,在kotlincompat模式下转换为kotlin.Pair<Success?, Failure?> - ✓
Set,在kotlincompat模式下转换为kotlin.collections.Set - ✓ 二元组,在
kotlincompat模式下转换为kotlin.Pair - ✓ 三元组,在
kotlincompat模式下转换为kotlin.Triple - ✓
URL,在kotlincompat模式下转换为java.net.URI - ✓
UUID,在kotlincompat模式下转换为java.util.UUID
不要依赖对象身份或使用 === 比较桥接实例。同一个对象可能被多个桥接实例包装。Kotlin/Java 类型内建的 equals 和 hashCode 默认使用对象身份,因此 Skip 会在原生类型的 Kotlin 投影中实现 equals 和 hashCode,让包装同一原生实例的不同对象比较结果相等,并具有相同的哈希值。
Skip 支持桥接 Equatable、Hashable 和 Comparable 类型。如果有更具体的比较或哈希需求,应为类型实现这些协议。
Skip 支持桥接自定义 Error 类型,也支持桥接可能抛出错误的函数。请注意:
- 任何遵循
Error的类型在转译为 Kotlin 后都会继承Exception,因此要桥接的 SwiftError类型不能是子类。 - 即使函数抛出的
Error类型本身没有被桥接,仍可以桥接该函数。但必须将这类函数视为可能抛出任意错误:请使用通用catch块,不要依赖捕获某个具体错误类型。
Kotlin 和 Swift 对泛型的实现策略非常不同。Swift 泛型深度集成在语言中,是其类型系统的一等公民。Kotlin 泛型则不存在于 JVM 层,只在编译期存在。此外,让 Swift 与 Kotlin 通信的 Java 原生接口(JNI)API 基于 C 语言,而 C 根本没有泛型。因此,泛型信息无法跨越 JNI 边界完整保留。
这些因素共同限制了泛型函数和泛型类型的桥接。无论是从已编译 Swift 桥接到 Kotlin,还是反向桥接,都需遵守以下限制:
- 不能桥接泛型类型的子类。
- 泛型类型的静态成员受限。Skip 只能支持不使用定义类型之泛型参数的静态成员,或能够转换为独立泛型函数的静态成员。
- 无法桥接由类型扩展定义的泛型特化(例如
extension C where T: Equatable)。 - 不支持泛型外层类型中的内层类型。
- Kotlin 不允许构造函数使用定义类型之外的泛型。
- Kotlin 不允许
typealias包含泛型约束(例如where T: Equatable)。
从转译后的 Kotlin 桥接到已编译 Swift 时,还有以下额外限制:
- 如果从一个确切类型未知的属性或函数(例如类型为
Any或某个协议类型)返回转译后的 Kotlin 泛型类型,那么传递到 Swift 时会丢失精确类型信息。例如,如果从桥接的 KotlinAny属性中读取C<T>实例,无论 Kotlin 端的T具体是什么,Swift 端都会收到C<Any>实例。同样,C<T: P>从Any字段桥接过来时会变成C<P>。
从已编译 Swift 桥接到 Kotlin 时不存在上述问题,但该方向有自己的限制:
- 不能在 Kotlin 中构造已桥接的 Swift 泛型类型实例。Swift 代码可以创建泛型类型并将其暴露给 Kotlin,但 Kotlin 代码无法自行构造 Swift 泛型实例。必须在 Swift 泛型类型的所有公开构造器上添加
// SKIP @nobridge注释,避免它们被桥接到 Kotlin。 - Kotlin 也无法访问 Swift 泛型类型的静态成员。公开静态成员同样必须添加
// SKIP @nobridge注释。 - 从 Kotlin 调用全局泛型函数时,不会保留泛型类型信息。因此,从 Kotlin 调用全局 Swift 函数
f<T>(p: T)时,它总会像T的类型为Any那样被调用。同理,f<T: P>(p: P)总会像T的类型为P那样被调用。
只有当原生可变结构体的投影会被转译后的 Swift 使用时,我们才建议对它进行桥接,因为此时 Skip 可以在 Kotlin 端维持值语义。如果计划从纯 Kotlin 或 Java 代码中使用原生类型,请使用类,因为类能对应 Kotlin/Java 的引用语义。
我们也不建议将转译后的可变结构体桥接到原生 Swift,因为这会导致 JVM 端进行大量对象拷贝。