跳转到内容
Skip
3.2k

转译参考

Skip 的 Swift 到 Kotlin 语言转译器能够转换 Swift 语言的大部分子集为 Kotlin。其结果就是我们所说的 Kotlish:语法上是 Swift 但在 Android 上作为 Kotlin 编译和运行的代码。转译器有以下目标:

  1. 避免生成有问题的代码。我们宁愿给你一个即时错误或生成完全无法编译的 Kotlin,也不愿生成能编译但行为与你的 Swift 源代码不同的 Kotlin。
  2. 让你编写自然的 Swift。Swift 是一门庞大的语言;我们尝试支持其最常见和最有用的功能,让你能够自信地编码。
  3. 生成惯用的 Kotlin。在可能的情况下,我们努力从你的 Swift 源代码生成干净且惯用的 Kotlin。

这些目标形成了一个层次结构。例如,如果生成更惯用的 Kotlin 会带来与源 Swift 产生微妙行为差异的风险,Skip 总是会选择不太惯用但没有问题的转译。

下表详细说明了 Skip 对转译各种 Swift 语言特性的支持。✓ 表示该特性得到完全或非常强的支持。~ 表示该特性得到部分支持。✕ 表示该特性不受支持或仅得到较弱的支持。未来的版本可能会解决一些不受支持的语言特性,但其他一些反映了 Swift 和 Kotlin 语言之间的深层不兼容性。

  • ✓ 类
    • ✓ 继承
    • Codable 合成
  • ✓ 结构体
    • ✓ 值语义。参见下面的结构体主题
    • ✓ 构造器合成
    • Equatable 合成
    • Hashable 合成
    • Codable 合成
  • ✓ 协议
    • ✓ 继承
    • ✓ 属性要求
    • ✓ 函数要求
    • where Self == Type
    • ~ 构造器要求。与泛型结合使用时支持有限
    • ~ 静态要求。与泛型结合使用时支持有限
  • ✓ 枚举
    • ✓ 带关联值的枚举
    • RawRepresentable 合成
    • CaseIterable 合成
    • Equatable 合成
    • Hashable 合成
    • ~ Codable 合成
      • Skip 只能为 RawRepresentable 枚举合成 Codable 一致性
  • ✓ 嵌套类型
    • ✓ 类型中定义的类型
    • ✕ 函数中定义的类型
  • ✓ 扩展
    • ✓ 具体类型扩展
    • ✓ 协议扩展
    • ~ 泛型特化的限制
    • ~ 扩展其他模块中定义的类型有限制
  • ✓ 泛型类型
    • ~ 参见下面的泛型主题了解限制
  • ✓ 元组
    • ✓ 有标签或无标签
    • ✓ 解构
    • ✓ 元数 2 到 5
    • ✕ 元数 6+
  • ✓ 类型别名
    • ✓ 嵌套类型别名
      • Skip 在转译过程中完全解析类型别名以解决 Kotlin 类型别名的限制
  • ✓ 属性
    • let
    • var
    • ✓ 静态属性
    • ✓ 存储属性
    • ✓ 计算属性
    • ✓ 可抛出属性
    • ✓ 延迟属性
    • ✓ 自定义 get/set
    • willSet
    • didSet
    • ✓ SwiftUI 属性包装器:@State@Environment
    • ✕ 自定义属性包装器
  • ✓ 函数
    • ✓ 基于类型的重载
    • ✓ 基于参数标签的重载
    • ✕ 基于返回类型的重载
    • ✓ 静态函数
    • ✓ 泛型函数
    • ✓ 可抛出函数
    • ✓ 在可变函数中赋值 self
    • ✓ 默认参数值
    • inout 参数
    • ✓ 闭包和尾随闭包
    • ✓ 可变参数
    • @autoclosure 参数
    • ✕ 参数包
  • ✓ 嵌套函数
  • ✓ 构造器
    • ✓ 可选构造器
    • ✓ 在构造器中赋值 self
    • ~ Kotlin 对在委托构造器中调用 super.initself.init 施加了一些限制
    • ✕ 构造器不能使用拥有类型未声明的泛型参数类型
  • ✓ 析构器
    • ~ deinit 被转译为 Kotlin 的 finalize。参见垃圾回收主题
  • ✓ 闭包
    • ✓ 显式和隐式($0$1 等)参数
    • ~ weak 和 unowned 捕获会被忽略。我们依赖 Kotlin 的垃圾回收
  • ✓ 错误处理
    • throw
    • do / catch
    • try, try?, try!
    • ✓ 抛出自定义枚举、结构体、类
    • ✓ catch 模式匹配
    • ✕ 错误类型不能是子类
  • ✓ 并发
    • Task / Task.detached
    • ✓ Task 组
    • async / await
    • ~ async let
      • 退出作用域时隐式任务组不会被取消
    • ✓ 异步函数
    • ✓ 异步属性
    • ✓ 异步闭包
    • AsyncSequence
    • AsyncStream
    • @MainActor
    • ~ 自定义 actor
      • 不支持非私有的可变属性。暴露函数来访问私有状态
    • ✕ Grand Central Dispatch
  • ✓ Defer
  • ✓ If
    • if let
      • 参见 If let 主题获取更多信息
    • if case
  • ✓ Guard
    • guard let
      • 参见 If let 主题获取更多信息
    • guard case
  • ✓ Switch
    • ✓ Case 模式匹配
    • ✓ Case 绑定
    • ~ 部分匹配和绑定的限制
    • case … where
  • ✓ While 循环
  • ✓ Do while 循环
  • ✓ For in 循环
    • for … in … where …
    • for let …
    • for case …
  • ✓ 运算符
    • ✓ 标准运算符
    • ✓ 逻辑运算符
    • ✓ 可选链
    • ✓ 可选解包
    • ✓ 范围运算符
    • ~ 切片运算符
      • 切片不可变
    • ~ 部分高级运算符不支持
    • ✓ 使用 == 的自定义 Equatable
    • ✓ 使用 hash(into:) 的自定义 Hashable
    • ✓ 使用 < 的自定义 Comparable
    • ~ 自定义下标运算符
      • 不能基于参数标签或类型重载下标运算符
    • callAsFunction 支持
    • ✕ 其他自定义运算符
  • ~ Key paths
    • ✓ 作为隐式闭包参数
    • ✓ 作为 @Environment
    • ✕ 其他用法
  • ✕ 宏
    • @Observable
    • @ObservationIgnored
    • ✕ 其他宏

下表详细说明了 Skip 对在转译代码中使用内建 Swift 标准库类型的支持。对这些类型的支持由 Skip 语言转译器和 SkipLib 开源库共同提供。

  • ✓ 数值类型
    • ✓ 使用 Kotlin 原生类型
    • ~ Int 在 JVM 上是 32 位
    • ~ 所有无符号和 Float 值必须是显式的 - 例如 Float(1.0);不支持从有符号类型或 Double 的隐式转换
  • String
    • ✓ 使用 Kotlin 原生 String
    • ✕ 不支持修改
  • AnyAnyObject
  • ✓ 可选值
    • ✕ Kotlin 不将 Optional 表示为独立类型,因此 .some.none 不存在
  • ✕ 复合类型(例如 A & B
  • Array
    • ✓ 值语义
    • ✓ 切片
  • Dictionary
    • ✓ 值语义
  • Set
    • ✓ 值语义
  • OptionSet
    • ~ 你必须使用 struct 实现 OptionSet
  • CaseIterable
    • ✓ 自动合成
    • ✓ 自定义实现
  • Codable
    • ✓ 自动合成
    • ✓ 自定义实现
  • CustomStringConvertible
  • Comparable
    • ✓ 自动合成
    • ✓ 自定义实现
  • Equatable
    • ✓ 自动合成
    • ✓ 自定义实现
  • Error
  • Hashable
    • ✓ 自动合成
    • ✓ 自定义实现
  • RawRepresentable
    • ✓ 自动合成
    • ✓ 自定义实现
  • ✓ `Result“
  • ~ Result builders
    • @ViewBuilder
      • 在覆盖 View.body 以外的 API 时,@ViewBuilder 属性不会被继承。请显式指定
    • ✕ 其他 result builders

Kotlish 是存在于 Swift 和 Kotlin 交叉点上的混合方言的非正式名称。它是存在于 Swift 源文件中的代码,被 Xcode 解析为有效的 Swift,被 Swift 编译器忽略,然后由 skipstone 插件转译为 Kotlin。其结果是一种混合语言:语法上是 Swift,语义上是 Kotlin,考虑到其偶然的设计,实际上出奇地实用。

Swift 和 Kotlin 无疑非常相似。它们都在 2010 年代被设计为面向老旧平台语言(分别是 Objective-C 和 Java)的现代替代品,并且得出了许多相同的结论:类型推断、空安全、尾随闭包、值类型与引用类型、一等函数和模式匹配等。它们的相似程度足以让大量代码在两种语言中只需进行微小的语法调整即可有效。

Skip 的转译器利用了这种相似性。当你在 #if SKIP 块中编写代码时,你编写的 Swift 语法会被 Skip 转换为 Kotlin。因为 Xcode 能看到代码但 Swift 编译器跳过它(该块被条件排除在 iOS 构建之外),你可以自由引用 Kotlin 和 Java 类型。Xcode 不会抱怨未定义的符号,因为它从不尝试编译该块。而且由于 Swift 和 Kotlin 在结构上非常相似,转译器的工作通常很直接:将 let 重命名为 val,在命名参数中将 : 替换为 =,在闭包中将 in 替换为 ->,其余部分自然水到渠成。

这就是 Kotlish。它不是语言规范。它没有版本控制。它是趋同语言设计的一个美妙巧合,被 Skip 的转译器进行了足够形式化以真正有用。

Kotlish 存在于 #if SKIP 块中。这些块中的所有内容必须在语法上是有效的 Swift,因为 Xcode 的解析器仍然会读取它。但它不需要在语义上是有效的 Swift,因为 Swift 编译器从不对其求值。这提供了很大的自由度和灵活性。

即使代码最终会成为 Kotlin,你也必须遵循 Swift 的语法规则。将 Kotlin 代码片段转换为 Kotlish 时最常见的陷阱:

命名参数 在 Swift 中使用冒号,而不是等号:

// Kotlin
someFunction(value = 42, label = "hello")
// Kotlish
someFunction(value: 42, label: "hello")

闭包参数 使用 in 关键字,而不是箭头:

// Kotlin
list.map { item -> item.name }
// Kotlish
list.map { item in item.name }

通配符导入 使用 .__ 而不是 .*,因为 .* 不是有效的 Swift:

// Kotlin
import com.google.maps.android.compose.*
// Kotlish
import com.google.maps.android.compose.__

变量声明 使用 letvar,而不是 valvar

// Kotlin
val name = "Skip"
// Kotlish
let name = "Skip"

一些 Kotlin 结构在 Swift 中没有语法等价物。最突出的是 :: 作用域解析运算符,在 Kotlin 中用于类引用和成员引用:

// 有效的 Kotlin,无效的 Swift
@OptIn(DelicateCoroutinesApi::class)
val ref = String::length

没有办法在 Swift 源代码中编写 :: 并让 Xcode 接受它。对于这些情况,你需要一个逃生舱。Skip 提供了几个,下面在逃生舱中有介绍。

其他需要变通方法的结构:

  • Kotlin when 表达式。 Swift 使用 switch,虽然 Skip 将 switch 转译为 when,但一些 Kotlin when 模式无法干净地映射回 switch
  • Kotlin companion object 声明。 Skip 自动处理静态成员,但显式的 companion object 块无法用 Swift 语法编写。
  • 带参数的 Java 风格注解。 一些注解如 @SuppressWarnings("unchecked") 可以用 Kotlish 编写,但复杂的注解语法可能需要 SKIP INSERT
  • 带自定义 getter 的 Kotlin 扩展属性。 Swift 扩展支持计算属性,所以这些通常可以工作,但对于更奇特的模式语法可能会有所不同。

当 Kotlish 达到其极限时,Skip 提供了一组注释指令,让你直接注入、替换或覆盖 Kotlin 代码片段。这些不是 hack;它们是 Skip 设计中有意为之的一部分,在跨平台主题文档中有说明。可以把它们想象成一栋组织良好的建筑中的紧急出口。

将原始 Kotlin 插入转译器输出。跟随其后的 Swift 代码(如果有)也会被正常转译。当你需要添加完全没有 Swift 等价物的内容时使用。

// SKIP INSERT: @OptIn(DelicateCoroutinesApi::class)
func post(_ notification: Notification) {
// Swift 实现...
}

@OptIn(DelicateCoroutinesApi::class) 注解包含被禁止的 :: 运算符,所以它不能作为 Swift 源代码存在。SKIP INSERT 注释将其直接注入 Kotlin 输出,下面的函数声明被正常转译。

你也可以使用 SKIP INSERT 定义完全没有 Swift 对应物的全新 Kotlin 结构:

// SKIP INSERT: fun <T> Array(data: Data): Array<T> = data.bytes as Array<T>

多行插入通过跨注释行继续工作:

// SKIP INSERT:
// var count by remember {
// mutableStateOf(100)
// }

用字面 Kotlin 替换紧随其后的 Swift 语句。Swift 代码在转译输出中被完全丢弃。当转译器为特定语句生成了不正确的 Kotlin 时使用。

// SKIP REPLACE: mac.init(secretKeySpec)
mac.init(secretKeySpec)

为什么需要这个?在这种情况下,转译器看到 .init( 并假设它是一个构造器调用,因为这就是 .init 在 Swift 中的含义。但在 Java Mac API 中,init 是一个普通方法。SKIP REPLACE 指令告诉转译器:“我知道我想要什么,直接原样输出。”

整个函数的多行替换:

// SKIP REPLACE:
// fun printOS() {
// print("Android")
// }
public func printOS() {
print("iOS")
}

在 iOS 上,printOS() 打印 “iOS”。在 Android 上,转译器忽略 Swift 函数体并从注释中发出 Kotlin 版本,打印 “Android”。

仅替换类型、函数或属性的声明,但保留转译的函数体。这是精确操作的选择:你控制在 Kotlin 中的声明方式,但让转译器处理实现。

// SKIP DECLARE: open class JSONEncoder: TopLevelEncoder<Data>
open class JSONEncoder {
// 函数体正常转译...
}

这里,Swift 声明 open class JSONEncoder 会被转译为没有超类的类。SKIP DECLARE 指令仅覆盖类头部以添加 : TopLevelEncoder<Data>,而内部的所有方法和属性仍然从 Swift 转译。

静默下一行的 Skip 转译器警告或错误。这是”我知道我在做什么”的指令:

// SKIP NOWARN
let dict = obj as? Dictionary<Int, String>

谨慎使用。如果你发现自己在到处使用 SKIP NOWARN,通常意味着方法需要重新思考而不是静默。

将整个 Swift 源文件标记为头文件。Skip 读取文件以收集类型和函数签名,但不转译函数体。你需要在 Kotlin 文件中提供实际实现。SkipLib 使用这种技术在手写的 Kotlin 中实现 Swift 标准库的部分。

在 Kotlin 输出中将函数标记为 external,表示其实现由原生代码提供(通常通过 JNI)。这用于 C 和 C++ 集成场景。

随着时间的推移,Skip 框架库中出现了一些惯用模式,代表了 Kotlish 最流畅的使用方式。如果你编写的转译代码需要大量与 Kotlin 或 Java API 交互,这些模式值得研究。

Skip 的基础库使用一种反复出现的模式,其中一个 Swift 结构体包装了一个”平台值”,即底层执行实际工作的 Kotlin 或 Java 类型:

#if SKIP
public struct Date: KotlinConverting<java.util.Date> {
internal var platformValue: java.util.Date
public init(platformValue: java.util.Date) {
self.platformValue = platformValue
}
public init(timeIntervalSince1970: TimeInterval) {
self.platformValue = java.util.Date((timeIntervalSince1970 * 1000.0).toLong())
}
public func kotlin(nocopy: Bool = false) -> java.util.Date {
return nocopy ? platformValue : platformValue.clone() as java.util.Date
}
}
#endif

Swift API 表面(DatetimeIntervalSince1970)对 iOS 开发者来说很熟悉。实现委托给 java.util.Date.kotlin() 方法在你需要将底层 Java 对象交给 Kotlin API 时提供显式转换。请参阅跨平台主题文档了解有关此模式的更多信息。

Kotlin 和 Java 有显式的数值转换方法(.toLong().toDouble().toByte().toInt()),这些在 Swift 标准库中不存在。在 #if SKIP 块中,你可以自由调用它们,因为 Swift 编译器从不对代码求值:

#if SKIP
let millis = (interval * 1000.0).toLong()
let bytes = array.map { $0.toUByte() }
let hexString = java.lang.Byte.toUnsignedInt(b).toString(radix: 16).padStart(2, "0".get(0))
#endif

这段代码对 Swift 开发者来说看起来有些陌生,但它是完全自然的 Kotlin。转译器会原样传递这些方法调用。

Kotlish 真正的威力在于无需任何桥接层即可调用 Android 平台 API:

#if SKIP
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
func createNotificationChannel() {
let context = ProcessInfo.processInfo.androidContext
let channel = NotificationChannel(
"default",
"Default",
NotificationManager.IMPORTANCE_DEFAULT
)
let manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
#endif

这个函数直接调用 Android 通知 API。没有桥接,没有包装器,没有生成的绑定。Swift 语法被转译为 Kotlin,Kotlin 正常调用 Android SDK。

Kotlish 也是你在 Skip 应用中嵌入 Jetpack Compose 视图的方式。@Composable 注解、remembermutableStateOf 和其他 Compose 原语都可以访问:

#if SKIP
import androidx.compose.runtime.__
struct MapComposer: ContentComposer {
let latitude: Double
let longitude: Double
@Composable func Compose(context: ComposeContext) {
GoogleMap(cameraPositionState: rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(
LatLng(latitude, longitude), Float(12.0)
)
})
}
}
#endif

有关 Compose 集成的完整介绍,请参阅跨平台主题文档的 Compose 集成部分。

Kotlish 在以下场景是正确的选择:

  • 直接从转译模块调用 Android 或 Java API。
  • 与第三方 Kotlin 库集成。
  • 实现 Skip 框架库尚未覆盖的平台特定功能。
  • 编写自定义 Compose 视图。

Kotlish 在以下场景不是正确的选择:

  • 你在编写 Skip Fuse(原生)模块。 在原生模式下,你的 Swift 由真正的 Swift 编译器编译,而非转译。使用 #if os(Android) 配合 AnyDynamicObject桥接转译模块代替。
  • 代码变得难以管理。 如果你的 #if SKIP 块增长到数百行 Kotlin 风格的 Swift 代码,每隔几行就有 SKIP REPLACE 注释,考虑编写一个 Kotlin 文件代替。Android Studio 会给你语法高亮、自动补全和编译器错误,而这些都是你在 Xcode 中编写 Kotlish 时无法获得的。
  • 你想要类型安全。 Xcode 无法检查 Kotlish 的语义正确性。在 Android 构建运行之前,你不会发现类型错误、缺失的方法或错误的参数类型。对于大量的 Android 特定逻辑,由 Kotlin 编译器编译的专用 Kotlin 文件更安全。

有关 Skip 转译器支持哪些 Swift 语言特性以及它们如何映射到 Kotlin 的完整列表,请参阅转译参考。该文档涵盖了转译器能力的全部范围,包括泛型、枚举、结构体、并发等方面的边缘情况。

有关 Skip 注释指令的完整列表及其语法,请参阅跨平台主题文档中的 Skip 注释


Skip 转译器执行大量有趣的代码转换,以弥合 Swift 和 Kotlin 之间的差异。以下部分讨论了值得解释的特定领域,因为转译会影响你代码的行为,或者生成的 Kotlin 在某些方面不寻常。

数值类型是双平台应用中细微运行时和编译问题的特别常见来源。运行时问题可能出现,因为 Kotlin 的 Int 是 32 位的。从技术上讲 Swift 的 Int 可以是 32 位或 64 位,具体取决于硬件,但苹果所有最近的设备都是 64 位的,所以 Swift 程序员倾向于假设 64 位整数。当你的代码需要超过 32 位整数值时,请注意使用 Int64。在 Java 中,溢出 32 位范围不会像 Swift 中那样导致错误,而是会静默地将 Int.max 回绕到 Int.min,使这类问题成为隐藏 bug 的潜在原因。

你还可能遇到 Android 编译问题,因为 Kotlin 在数值类型之间的转换方面可能很挑剔。通常,在使用 IntDouble 以外的任何类型时,你应该显式转换。例如,如果 var fFloat,请写 f = Float(1.0) 而不是 f = 1.0。此外,虽然 IntDouble 不需要显式类型转换,但 Kotlin 不允许你将整数字面量赋值给双精度变量或参数。例如,如果 var dDouble,Kotlin 要求你写 d = 1.0 而不是 d = 1。Skip 会尝试在需要时将你的整数字面量转换为小数,但有时你可能需要将 Double 值写为 1.0 而不是 1

Skip 不包装 Kotlin 的基本类型。我们选择了直接使用 Kotlin 基本类型所带来的巨大效率和互操作性优势,而不是如果我们用自己的类包装 Kotlin 基本类型可能实现的额外 Swift 语言兼容性。

这意味着我们必须接受 Kotlin 基本类型的现状,它们有一些会影响你代码的限制。最重要的是这些类型是不可变的。不支持 Bool.toggle() 等函数,并且 String 在 Skip 代码中是不可变的。你不能就地 appendString,而必须创建一个新字符串。你不能调用 String.sort(),而必须调用 let sorted = string.sorted() 等。此外,String 不是 Collection。虽然我们已经为 String 添加了 Collection API,但没有办法将新协议添加到现有 Kotlin 类型中。所以虽然你可以调用所有习惯的 Collection API,但不能将 String 传递给期望 Collection<Character> 的代码。

Swift 使用自动引用计数来决定何时释放对象的内存。Kotlin 使用垃圾回收。这个差异有重要的后果,你需要牢记:

  • 在 Android 上,你的 deinit 函数会在不确定的时间被调用,甚至可能根本不会被调用。虽然 Swift 会在对象的引用计数达到零时立即调用 deinit 函数并释放内存,但在 Android 上这些任务的时机完全由垃圾回收器决定。
  • Android 垃圾回收器可以检测并清理引用循环。在 Swift 中,weakunowned 修饰符最常见的用法是避免强引用循环。这在 Kotlin 中不是问题,因此 Kotlin 也没有这些修饰符。Skip 选择忽略属性和闭包捕获列表中的 weakunowned 修饰符,转而依赖垃圾回收器。如果你打算使用 weakunowned 引用是出于避免强引用循环以外的原因,你应该考虑替代方案。

所有 Kotlin 对象都是引用类型。除了 Int 等基本类型外,没有值类型。为了让你能够使用 Swift 结构体同时确保 Android 程序中的行为一致,Skip 使用了自己的 MutableStruct 协议。

Skip 自动将所有可变结构体类型添加 MutableStruct 协议。它使用此协议的函数赋予 Kotlin 类值语义。当你检查从使用可变结构体类型的 Swift 转译出的任何 Kotlin 时,你会注意到:

  • 你的可变结构体类型的 Kotlin 类会采用 MutableStruct 协议并实现其必需的函数。
  • 你会看到 .sref() 调用散布在你的代码中。这代表结构体引用。Skip 在需要复制表示结构体的 Kotlin 对象以维护值语义时添加 sref() 调用 - 例如将结构体赋值给变量时。
  • 持有可变结构体类型的属性会获得自定义 getter 和 setter 代码,以在需要时复制传入和传出的值。
  • 返回可变结构体类型的函数会对返回的值调用 sref()

虽然现代虚拟机非常擅长管理大量对象,但在极端情况下你可能想要修改代码以避免过度复制。我们建议你在看到性能问题之前不要担心。

对于结构体在技术上是可变的但在设置属性后永远不会被修改的情况 - 即配置对象 - 添加 @nocopy 属性。这指示 Skip 将结构体视为不可变的并避免复制。

// SKIP @nocopy
struct S {
}

无法回避:Swift 泛型很复杂。而从 Swift 泛型转换到 Kotlin 泛型则更加复杂,因为这两种语言有非常不同的泛型实现策略。Swift 泛型作为一等公民深度内建于其类型系统中。另一方面,Kotlin 泛型在 JVM 级别不存在,仅在编译时存在。

这种差异有深远的影响。例如,因为泛型内建于 Swift 的类型系统,Dictionary<Int, String>.Entry 是一个 Swift 类型。但在 Kotlin 中,等价类型是 Dictionary.Entry<Int, String>。当用作其他类型或静态成员的作用域时,Dictionary 的泛型消失了。

幸运的是,Skip 能够桥接两种语言之间足够多的差异,你在日常使用中可能不会遇到问题。Skip 完全支持:

  • ✓ 使用内建泛型数据结构如 ArrayDictionarySet
  • ✓ 定义你自己的泛型类、结构体、枚举
  • ✓ 定义和一致性带 associatedtypes 的协议
  • ✓ 泛型函数
  • ✓ 泛型约束如 where T: Equatable

但 Skip 能克服的不兼容性是有限度的。以下特性不受良好支持:

  • ~ 泛型类型的静态成员有限。Skip 只能支持不使用定义类型泛型的静态成员,或者可以转换为独立于定义类型泛型定义的泛型函数
  • ~ 通过类型扩展进行泛型特化(例如 extension C where T: Equatable)有限
  • ✕ 不支持泛型外部类型上的内部类型 - 参见上面的 Dictionary 示例
  • ✕ Kotlin 不允许构造器函数使用定义类型以外的泛型
  • ✕ Kotlin 不允许 typealiases 包含泛型约束(例如 where T: Equatable
  • is 测试和 as? 转换不考虑类型签名的泛型部分,因为泛型类型在运行时不存在

Skip 转译器通常会检测不支持的模式并提供适当的错误消息。不过,你也可能会遇到额外的限制。我们的一般建议是在简单的用例中利用泛型,但避免复杂的泛型定义和约束。

在 Kotlin 中保留泛型信息的一种方法是使用带具体化类型的内联函数。你可以在 Kotlin 语言文档中阅读有关此主题的更多信息。Skip 自动将任何带 @inline(__always) 属性的 Swift 函数转换为带具体化泛型的 Kotlin 内联函数。

@inline(__always) public func f<T>(param: T) {
...
}

转译为:

inline fun <reified T> f(param: T) {
...
}

Skip 不支持 Grand Central Dispatch。相反,它支持 Swift 的现代并发,包括 asyncawaitTaskTaskGroup,以及 actor。

注意 @MainActor 和自定义 actor 都不是 Kotlin 的特性。Skip 通过添加自己的调用来支持 actor,以跳转到 actor 的隔离上下文并从中返回。你会在生成的 Kotlin 中看到这些插入的调用,它们可能看起来令人惊讶。

目前 @MainActor 不会自动从超类和协议成员继承。请显式将属性添加到所有覆盖。不过,Skip 确实为 View.body 做了例外 - 你的 View 体会自动绑定到 @MainActor

Skip 将枚举转译为 Kotlin 枚举,并创建类似的 case 语句。有一些 Kotlin 不支持的复杂 case 匹配结构,意味着你需要找到替代方式来用 Swift 表达逻辑。这些限制如下:

  1. 无法翻译带关联值的复合 case 匹配,如 case .caseA(let value), .caseB(let value):
  2. 无法翻译同时条件检查值的 case 匹配,如 case .caseA(let value) where value == "X"

Swift 的 if let x = f()(或 guard let x = f())语法同时做了几件事:

  1. 精确执行 f() 一次。
  2. 测试值不为 nil
  3. 将值绑定到具有适当作用域的新变量。

虽然 Kotlin 的 if (x != null) 检查确实有一些智能 - Kotlin 通常会让你在 if 块体中将 x 视为非空 - 但没有 Kotlin 语言结构能完成 if let 所做的所有事情。因此,根据 Swift 代码使用 if let 的细节,Skip 可能需要生成大量 Kotlin 以确保跨平台的行为一致。这包括生成嵌套的 if 语句并可能复制整个 else 代码块。虽然生成的 Kotlin 看起来可能很复杂,但它与原始 Swift 一样高效。

以下示例展示了 Skip 转译器将为各种常见 Swift 代码生成的大致 Kotlin 代码。这些可能会因项目上下文和 Skip 版本而异(这些是使用 Skip 1.7.0 生成的)。

class MyClass {
static let staticValue = 1
var stringField = "abc"
var intField = 123
var doubleField = 456.78
}
public class Base {
var type: String
init(type: String = "Base") {
self.type = type
}
}
internal class Sub : Base {
var level: Int
init(level: Int) {
super.init(type: "Sub")
self.level = level
}
}
struct MyStruct {
let str: String = "abc"
let num: Int
}
// Skip 为可变结构体添加方法,使 Skip 能够复制 Swift 结构体的值语义
struct MyStruct {
var str: String = "abc"
var num: Int
}
struct MyStruct : Hashable, Codable {
var str: String = "abc"
var num: Int
}
protocol MyContract {
var name: String { get }
func perform() throws -> Int
}
protocol MyContract {
var name: String { get }
func perform() throws -> Int
}
extension MyContract {
var name: String {
"default"
}
}
protocol MyContract {
associatedtype R
var name: String { get }
func perform() throws -> R
}
class MyClass: MyContract {
var name: String
var value: Int
init(name: String, value: Int) {
self.name = name
self.value = value
}
func perform() -> Int {
return value
}
}
enum Size {
case small, medium, large
}
enum Position : CaseIterable {
case one, two, three, four, five
}
enum Size : String {
case small, medium, large = "LG"
}
// 密封类是 Kotlin 中与带关联值枚举最接近的类比
enum E : Hashable {
case option1
case option2(Int, String)
}
func process(e: E) {
if case .option2(let i, _) = e {
print("i = \(i)")
}
}
let d = [1: "a", 2: "b", 3: "c"]
for (key, value) in d {
print(key)
print(value)
}
// Kotlin 没有可选构造器。Skip 复制了它们的行为
class C {
let i: Int
init?(param: Int) {
if param == 0 {
return nil
} else {
i = param
}
}
}
func f() -> Int {
return C(param: 0)?.i ?? -1
}
func g() -> C {
return C(param: 0)!
}
func compute(_ input1: Int, _ input2: Int) async throws -> Int {
// 执行一些开销大的操作
return input1 * input2
}
func printResult() async throws {
let result = try await compute(3, 5)
print(result)
}
enum Size {
case small, medium, large
}
func mySizes() -> [Size] {
// Kotlin 没有隐式限定成员
[.medium, .large]
}
import SwiftUI
struct ContentView : View {
@State var value = 0.0
var body: some View {
VStack {
Text("Current value: \(value)")
Slider(value: $value)
Button("Reset") {
value = 0.0
}
}
}
}
// 使用 #if os(Android) 条件包含 Android 和非 Android 平台的代码
func languageName() -> String {
#if os(Android)
"Kotlin"
#else
"Swift"
#endif
}
func languageName2() -> String {
#if !os(Android)
"Swift"
#else
"Kotlin"
#endif
}
// Skip 有一组特殊注释用于指定 Kotlin 代码
func languageName() -> String {
// SKIP REPLACE: return "Kotlin"
"Swift"
}