跳转到内容
Skip
3.2k

桥接参考

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
  • ✓ 数值类型
    • ~ IntUInt 在 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 类型内建的 equalshashCode 默认使用对象身份,因此 Skip 会在原生类型的 Kotlin 投影中实现 equalshashCode,让包装同一原生实例的不同对象比较结果相等,并具有相同的哈希值。

Skip 支持桥接 EquatableHashableComparable 类型。如果有更具体的比较或哈希需求,应为类型实现这些协议。

Skip 支持桥接自定义 Error 类型,也支持桥接可能抛出错误的函数。请注意:

  • 任何遵循 Error 的类型在转译为 Kotlin 后都会继承 Exception,因此要桥接的 Swift Error 类型不能是子类。
  • 即使函数抛出的 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 时会丢失精确类型信息。例如,如果从桥接的 Kotlin Any 属性中读取 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 端进行大量对象拷贝。