Lite 与 Fuse 模式
Skip 同时支持编译模式的 “Fuse” —— 将 Swift 原生编译为 Android 代码,以及转译模式的 “Lite” —— 将 Swift 转译为 Kotlin。模式在 Swift 模块级别指定。我们将主要使用原生模式的应用称为 Skip Fuse 应用,将主要使用转译模式的应用称为 Skip Lite 应用。
- Skip Fuse(原生模式):你的 Swift 通过 Swift 官方 Android SDK ↗ 进行原生编译。你可以获得完整的 Swift 语言支持、标准库和 Foundation。
- Skip Lite(转译模式):你的 Swift 源代码会被转换为等效的 Kotlin 源代码,再由标准 Kotlin 编译器编译。这最大限度地提升了与 Kotlin 和 Java 库的互操作性。
模式按 Swift 模块选择,因此同一个应用中的不同模块可以使用不同模式。我们将主要使用原生模式的应用称为 Skip Fuse 应用,将主要使用转译模式的应用称为 Skip Lite 应用。
原生模式:Skip Fuse
Section titled “原生模式:Skip Fuse”Skip 的原生模式使用 Swift SDK for Android ↗ 将 Swift 原生编译,也是我们推荐给大多数应用使用的模式。它由以下部分组成:
- 面向 Android 的原生 Swift 工具链。
- 将 Swift 的日志、网络等功能与 Android 操作系统集成。
- 桥接技术:支持从编译后的 Swift 调用 Kotlin/Java API,以及从 Kotlin/Java 调用编译后的 Swift API。
- Xcode 集成与工具,用于跨 iOS 和 Android 编译和部署 Swift。
下图展示了一个原生项目的架构:
Skip 的文档讨论了其相较于分别编写应用或使用其他跨平台技术的优势。但为什么选择原生而非转译的 Swift 呢?以下是你可能倾向编译 Swift 的几个重要原因:
- 完整的 Swift 语言。转译器支持 Swift 的大部分特性,但某些语言特性(如特定的泛型模式和运算符重载)无法映射到 Kotlin。原生模式没有这些限制。
- 忠实的运行时行为。Swift 的确定性对象释放机制无法在 JVM 上复制,因为 JVM 使用垃圾回收。原生模式在 Android 上保留了 Swift 的内存管理语义。
- 完整的标准库和 Foundation。SkipLib 和 SkipFoundation 库用 Kotlin 实现了常用的 Swift 标准库和 Foundation API。而 Android 上的原生 Swift 使用苹果自己的 Foundation 实现,覆盖更加完整。
- 第三方 Swift 库。数千个 Swift 包 ↗已经可以编译到 Android,移植其他包通常也很简单。你可以直接使用这些包,无需将它们转换为 Skip 模块。
- 无缝的 C 和 C++ 集成。Android 上的原生 Swift 通过 Swift 内置的互操作机制直接访问 C 和 C++ 库,与 iOS 上的体验一致。转译路径则需要使用 SkipFFI 框架,功能相对有限。
- 计算密集型代码的性能更优。Swift 的值类型(结构体和枚举)可以分配在栈上,释放也是确定性的。这避免了可能影响 JVM 代码的垃圾回收暂停和堆压力。
使用原生 Swift 并非没有代价。以下是相较于 Skip 转译模式的一些主要劣势:
- 应用体积。捆绑 Swift 标准库、Foundation 和 Swift 国际化库会使最终 Android 应用包体积增加约 60 MB。我们正在探索在后续版本中减少这一开销的方法。
- Kotlin/Java 桥接。转译的 Swift 可以直接与 Kotlin 和 Java API 交互。因此,Skip 允许你的原生 Swift 集成转译代码。但编译后的 Swift 与 Kotlin 或 Java 之间传递数据仍然需要桥接。
- 调试。在 Android 上调试原生代码比调试生成的 Kotlin 更困难,后者可以充分利用 Android Studio 内置的 Kotlin/Java 调试工具。
- 构建时间。使用 Android 工具链构建和部署原生 Swift 比使用转译方式更慢,因为原生编译和将共享对象文件打包到
.apk中存在额外开销。 - 可脱离性。使用转译代码时,你可以停止使用 Skip,继续将 iOS 应用作为 Swift 开发,Android 应用则使用生成的 Kotlin。而在原生模式下,停止 Skip 后你会得到一个可运行的 iOS 应用,但没有独立的 Android Kotlin 代码库。
对于包含复杂业务逻辑和第三方 Swift 依赖的应用,Skip Fuse 的优势通常大于劣势。其劣势(二进制体积、构建时间)也预计会随着 Swift Android SDK 的成熟而改善。转译模式在需要直接访问 Kotlin/Java API 的库中仍然表现出色,许多应用会结合两种模式:Fuse 用于业务逻辑,Lite 用于平台服务集成。
转译模式:Skip Lite
Section titled “转译模式:Skip Lite”转译是将一种计算机语言转换为另一种具有相似抽象级别的语言的过程。Skip 的转译模式将你的 Swift 源代码转换为等效的 Kotlin 以在 Android 上运行。它由以下部分组成:
- Swift 到 Kotlin 的转译器。
- 一套开源库,为转译的 Swift 镜像 Swift 标准库和 Foundation 等核心框架。
- Xcode 集成与工具,用于跨 iOS 和 Android 转译和部署 Swift。
下图展示了一个纯转译项目的架构:
转译模式的优势和劣势与原生模式几乎完全相反。
- 与 Android API 的集成。转译的主要好处是与 Android 的 Kotlin 和 Java API 近乎完美地集成。由于你的 Swift 代码会被转换为 Kotlin,因此它可以直接调用其他 Kotlin API,就像调用 Swift 一样。我们称这种混合方言为 Kotlish:语法上是 Swift,语义上是 Kotlin。而且由于 Kotlin 语言与 Java 无缝集成,你的 Swift 代码也可以调用 Java API。与原生模式不同,无需桥接。
- 透明性。转译让你可以查看和理解 Skip 的所有输出。Skip 生成的 Kotlin 完全可读,甚至可以覆盖:Skip 支持在 Swift 代码中插入或替换字面量 Kotlin。这在调试时特别有用,你可以获得完整的堆栈跟踪,并充分利用 Android 的调试工具单步调试生成的 Kotlin。
- 可脱离性。如果 Skip 不再可用,转译模式仍然为你提供应用 iOS 和 Android 版本的完整源代码。你可以继续将应用作为独立的 iOS 和 Android 代码库发展(这也是许多双平台应用的开发方式)。除了用于在 Android 上提供 Foundation、SwiftUI 等 API 的库之外,Skip 没有任何必需的运行时组件,而这些库都是免费开源的 ↗。
- 应用体积。转译应用无需捆绑任何额外内容,只需 Skip 相对精简的兼容库,而原生应用必须包含更大的 Swift Foundation 和国际化库。
- 构建时间。Skip 转译器与 Android Kotlin 编译器的组合在迭代 Android 代码时比使用完整的原生 Swift 工具链更快。
- 不是真正的 Swift。虽然 Skip 的智能 Swift 到 Kotlin 转译器可以翻译绝大部分 Swift 语言,但有些语言特性无法映射到 Kotlin。此外,Swift 运行时行为的某些方面在转换为 Kotlin 并在 JVM 上运行后永远不会完全相同。例如,Swift 的确定性对象释放无法在 JVM 上复制,因为 JVM 使用不确定性的垃圾回收来管理内存。
- 标准库和 Foundation 覆盖有限。
SkipLib和SkipFoundation库用 Kotlin 复制了大部分 Swift 标准库和 Foundation。但 Android 上的原生 Swift 覆盖更加完整。 - 第三方库较少。Skip 的第三方转译库相对较少。与此同时,数千个 Swift 包 ↗可以编译到 Android,而且移植其他包通常也不难。
- C 和 C++ 集成。虽然 SkipFFI 框架允许转译的 Kotlin 与 Android 上的原生 C 代码交互,但创建两种语言之间接口的过程可能比较繁琐。Android 上的原生 Swift 解锁了 Swift 与 C 和 C++ 语言的出色集成。
- 性能。Android 上的 Java 非常快,但它是一个垃圾回收和堆分配的运行时,通常内存占用较高。原生 Swift 的值类型(如结构体和枚举)可以分配在栈上,提供设备上最快的裸机性能。
转译模式与 Kotlin 和 Java API 交互的便利性使其非常适合与 Android 平台服务紧密集成的用例,例如 Skip Keychain ↗ 等跨平台库。然而,对于共享业务逻辑和其他通用用例,我们认为大多数开发者会选择原生模式——主要是因为其完整的语言覆盖和第三方包的可用性。许多应用会结合使用原生应用逻辑和转译库来访问平台服务。
每个 Skip 模块必须在其源目录中包含一个 Skip/skip.yml 文件。当你创建新项目时,skip create 命令会自动创建此文件。你在 skip.yml 中声明的 mode 决定了模块是作为原生 Swift 编译还是转译为 Kotlin:
skip: mode: 'native'|'transpiled'如果未指定 mode,则默认为 'transpiled'。一个项目可以包含原生模块和转译模块的混合。正如我们稍后将看到的,你还需要在 skip.yml 中配置原生 Swift 与转译 Swift/Kotlin/Java 之间的桥接。
skip create 命令还会在 Package.swift 中为目标模式生成适当的依赖。对于模型层代码,原生模块通常依赖 SkipFuse 和 SkipModel,而转译模块仅依赖 SkipModel。对于 UI 代码,原生模块依赖 SkipFuseUI,而转译模块依赖 SkipUI。以下是一个包含独立模型和逻辑模块的原生应用的 Package.swift 示例:
import PackageDescription
let package = Package( name: "skipapp-hiya", defaultLocalization: "en", platforms: [.iOS(.v17), .macOS(.v14), .tvOS(.v17), .watchOS(.v10), .macCatalyst(.v17)], products: [ .library(name: "HiyaSkipApp", type: .dynamic, targets: ["HiyaSkip"]), .library(name: "HiyaSkipModel", type: .dynamic, targets: ["HiyaSkipModel"]), .library(name: "HiyaSkipLogic", type: .dynamic, targets: ["HiyaSkipLogic"]), ], dependencies: [ .package(url: "https://github.com/skiptools/skip.git", from: "1.2.7"), .package(url: "https://github.com/skiptools/skip-model.git", from: "1.0.0"), .package(url: "https://github.com/skiptools/skip-fuse.git", from: "1.0.0"), .package(url: "https://github.com/skiptools/skip-fuse-ui.git", from: "1.0.0") ], targets: [ .target(name: "HiyaSkip", dependencies: [ "HiyaSkipModel", .product(name: "SkipFuseUI", package: "skip-fuse-ui") ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]), .target(name: "HiyaSkipModel", dependencies: [ "HiyaSkipLogic", .product(name: "SkipModel", package: "skip-model"), .product(name: "SkipFuse", package: "skip-fuse") ], plugins: [.plugin(name: "skipstone", package: "skip")]), .testTarget(name: "HiyaSkipModelTests", dependencies: [ "HiyaSkipModel", .product(name: "SkipTest", package: "skip") ], plugins: [.plugin(name: "skipstone", package: "skip")]), .target(name: "HiyaSkipLogic", dependencies: []), ])通常情况下,原生代码与 Kotlin/Java 通信的唯一方式是通过有限且繁琐的 Java Native Interface ↗,而 Kotlin/Java 与原生代码通信的唯一方式是通过 “native” 或 “external” 函数 ↗。
然而,正如 Xcode 可以生成“桥接头文件”让 Swift 和 Objective C 互操作一样,SkipStone 构建插件可以生成代码,使你的原生 Swift 与 Android 服务和 UI 的 Kotlin/Java 世界之间透明交互。桥接允许你自动将编译的 Swift 类型和 API 投射到 Kotlin/Java,并将转译的 Swift 以及其他 Kotlin/Java 类型和 API 投射到原生 Swift。桥接后的 API 可以像用目标语言编写的一样使用,就像 Swift 可以使用桥接的 Objective C 一样。
你可以对任何 Skip 模块使用桥接——即任何包含 SkipStone 构建插件和 skip.yml 文件的模块。要桥接一个类型或 API,在其声明处添加 // SKIP @bridge Skip 注释。以下示例桥接了 Person 类但不包括其 age 属性。
// SKIP @bridgepublic class Person { // SKIP @bridge public init() { ... }
// SKIP @bridge public var firstName = ""
// SKIP @bridge public var lastName = ""
public var age = 0
// SKIP @bridge public func fullName() -> String { ... }
...}假设这是一个原生 Swift 模块,桥接允许 Person 在 Kotlin 代码中使用:
fun personClient(): String { val p = Person() p.firstName = "Tim" p.lastName = "Apple" // p.age = 10 This would be a compile error! Age is not bridged return p.fullName()}以下章节将详细介绍 Swift 到 Kotlin/Java 和 Kotlin/Java 到 Swift 的桥接。
要桥接一个类型并默认包含其所有成员,使用 // SKIP @bridgeMembers。这将桥接所有与类型声明可见性相同或更高的成员。换句话说,将其添加到 public 类型将桥接所有 public 成员,而将其添加到默认可见性的类型将桥接所有至少具有默认可见性的成员。要排除某个成员不被桥接,添加 // SKIP @nobridge 注释。以下是使用 @bridgeMembers 的 Person 类:
// SKIP @bridgeMemberspublic class Person { public init() { ... }
public var firstName = ""
public var lastName = ""
// SKIP @nobridge public var age = 0
public func fullName() -> String { ... }
...}你可以在 skip.yml 中启用自动桥接,而不必逐个标注每个 API。当模块启用自动桥接时,所有 public API 默认都会被桥接。要开启自动桥接,只需在模块的 skip.yml 中添加 bridging: true。无需额外配置。
例如,以下 skip.yml 配置了一个原生 Swift 模块,其 public 类型和 API 将被桥接以供 Kotlin/Java 代码使用:
skip: mode: 'native' bridging: true使用 // SKIP @nobridge 可以排除特定类型或 API 不被自动桥接。以下是自动桥接下与之前等效的 Person 定义:
public class Person { public init() { ... }
public var firstName = ""
public var lastName = ""
// SKIP @nobridge public var age = 0
public func fullName() -> String { ... }
...}注意,bridging: true 实际上是以下完整配置语法的简写,我们将在下文看到更多:
skip: mode: 'native' bridging: auto: trueSwift 到 Kotlin/Java 的桥接
Section titled “Swift 到 Kotlin/Java 的桥接”在原生 Swift 模块上启用桥接后,其 API 可以从 Kotlin/Java 代码中使用。最常见的用例是用原生 Swift 编写应用的业务逻辑,然后启用桥接以便自定义的 Android UI 层可以访问它。
当 Skip 将原生 Swift 桥接到 Kotlin 时,它会生成委托给原生 Swift 的 Kotlin 包装器。默认情况下,这些包装器针对 Skip 的转译 Swift 消费进行了优化。例如:
// In module NetworkUtils
public class URLManager { public var urls: [URL] = []
public func perform(_ action: (URL) -> Void) { ... }
...}生成如下 Kotlin 包装器:
package network.utils
class URLManager { var urls: skip.lib.Array<skip.foundation.URL> get() { // Delegate to native code ... } set(newValue) { // Delegate to native code ... }
fun perform(action: (skip.foundation.URL) -> Unit) { // Delegate to native code... }
...}这允许任何其他 Kotlin/Java 代码与此 API 交互,但请注意它使用了 skip.lib.Array 和 skip.foundation.URL。这些是来自 Skip 开源库套件的类型,用于在 Kotlin 中镜像 Swift API。当你的 API 将被转译的 Swift 消费时使用它们是合理的,因为它们与 Skip 转译输出中使用的类型相同。你可以获得完美的互操作性,包括预期的 Swift 行为,如 Array 结构体的值语义。
Kotlin 兼容选项
Section titled “Kotlin 兼容选项”如果你计划直接从 Kotlin/Java 消费原生 Swift——例如,为 Android 编写 Compose UI——你可以配置 skip.yml 以优化 Kotlin 兼容性:
skip: mode: 'native' bridging: auto: true options: 'kotlincompat'在此配置下,生成的 Kotlin 包装器将使用标准 Kotlin/Java 类型:
package network.utils
class URLManager { var urls: kotlin.collections.List<java.net.URI> get() { // Delegate to native code ... } set(newValue) { // Delegate to native code ... }
fun perform(action: (java.net.URI) -> Unit) { // Delegate to native code... }
...}有关哪些标准类型和 Swift 语言结构可以被桥接的更多信息,请参见桥接支持参考。
Kotlin/Java 到 Swift 的桥接
Section titled “Kotlin/Java 到 Swift 的桥接”Skip 提供了多种在原生 Swift 中消费 Kotlin/Java API 的选项。最全面的方法是桥接一个转译的 Swift 模块。
Skip 将 Swift 转译为 Kotlin 的能力是其优势之一。转译的 Swift 代码实际上就是 Kotlin(我们称这种混合方言为 Kotlish),这赋予了它一个超能力:它可以直接调用任何其他 Kotlin/Java API,无需桥接或配置,就像它们是 Swift 编写的一样。Skip 文档提供了详细信息。
当你在转译模块上启用桥接并将其 API 暴露给原生 Swift 时,你也在暴露这个超能力。怎么做?让我们看一个例子。假设你的原生 Swift 想要将敏感数据存储在 Android 的加密共享偏好中,而这些只能通过 Kotlin 访问。我们选择创建一个可复用的库来将此功能暴露给 Swift。
首先,我们创建一个转译模块并启用桥接:
skip: mode: 'transpiled' bridging: true然后我们编写 Swift 接口来访问 Android 服务,根据需要直接调用 Android 的 Kotlin API。请记住,我们可以这样做是因为 Skip 正在将 Swift 转译为 Kotlin 用于 Android 构建。
#if !SKIP_BRIDGE#if os(Android)import Foundation
import android.content.Contextimport android.content.SharedPreferencesimport android.security.keystore.KeyGenParameterSpecimport android.security.keystore.KeyPropertiesimport androidx.security.crypto.EncryptedSharedPreferencesimport androidx.security.crypto.MasterKeys
/// Secure storage using Android encrypted shared preferences.public final class SecureStorage { /// The shared keychain. public static let shared = SecureStorage()
private let lock = NSLock()
/// Retrieve a value. public func string(forKey key: String) -> String? { let prefs = initializePreferences() return prefs.getString(key, nil) }
// Additional getters omitted...
/// Store a key value pair. public func set(_ string: String, forKey key: String) { let prefs = initializePreferences() let editor = prefs.edit() editor.putString(key, string) editor.apply() }
// Additional setters omitted...
private var preferences: SharedPreferences?
private func initializePreferences() -> SharedPreferences { lock.lock() defer { lock.unlock() }
if let preferences { return preferences }
let context = ProcessInfo.processInfo.androidContext let alias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) preferences = EncryptedSharedPreferences.create("tools.skip.SecureStorage", alias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) return preferences! }}#endif#endif最后,我们将此模块添加为原生 Swift 模块的依赖。现在我们的原生 Swift 可以像使用其他 Swift 类型一样导入和使用 SecureStorage!
桥接示例图:SkipKeychain
Section titled “桥接示例图:SkipKeychain”以下可视化图总结了 Skip 生成的桥接如何在 SkipKeychain ↗ 框架中工作,该框架暴露了一个直接的 Swift 接口来访问仅限 iOS 的 Security Keychain 服务 ↗框架,其转译版本在 Android 上使用 androidx.security.crypto.EncryptedSharedPreferences ↗ 包中的等效功能。
AnyDynamicObject
Section titled “AnyDynamicObject”我们认为,创建类似 SkipKeychain ↗ 这样的 Swift-API 模块生态系统来访问跨平台和仅限 Android 的服务是最佳的长期方案,而使用转译的 Skip 模块是一个很好的实现路径。你也可以在原生模块中嵌入转译的 Swift。然而,有时你可能希望在不使用转译 Swift 的情况下从原生 Swift 使用几个 Kotlin/Java 类型。对于这种用例,我们提供了 AnyDynamicObject 类型。
AnyDynamicObject 是由 SkipFuse 框架提供的原生 Swift 类,可以表示任何 Kotlin/Java 类型。它利用 Swift 的动态特性让你访问任何属性或调用任何函数,并利用 Kotlin/Java 强大的反射能力在底层 JVM 对象上调用你的方法。以下是裸用 AnyDynamicObject 的示例:
import SkipFuse
...
// Specify the Kotlin or Java class name and any constructor argumentslet date = try AnyDynamicObject(className: "java.util.Date", 999) // java.util.Date(999)
// All calls must specify their return type and unwrap if non-optional// All function calls are `throws`let time1: Int64 = try date.getTime()! // 999
// Like Kotlin, `AnyDynamicObject` allows property syntax for Java getters and setters// Property access is not `throws`let time2: Int64 = date.time! // 999
// Java calls must use positional args. Kotlin calls may use labeled args and omit defaulted args// Even `Void` function calls need to specify a return typetry date.setTime(1000) as Void
// But property setters do notdate.time = 1001
// Retrieve other objects as `AnyDynamicObject`let instant: AnyDynamicObject? = date.instantlet s1: String? = try instant?.toString()
// Freely chain calls through other objectslet s2: String = try date.instant!.toString()!let s3: String = try date.getInstant()!.toString()!
// Types with bridging support can be assigned from their Kotlin/Java equivalents// See the Bridging Support Referencelet urls: [URL] = // ... call that returns any kotlin.collections.List<java.net.URI>然而,这种裸用 AnyDynamicObject 的方式有一些劣势。构造对象总是需要指定完整的类名,而且无法使用 typealias。访问静态成员也不够直观:需要创建另一个使用不同构造函数的 AnyDynamicObject:
let dateStatics = try AnyDynamicObject(forStaticsOfClassName: "java.util.Date")let date: AnyDynamicObject? = try? dateStatics.parse(dateString)Skip 为这些问题提供了一些可选的语法糖。要使用它,在 skip.yml 配置中设置一个 dynamicroot 字符串。指定的字符串会变成一个魔法命名空间,你可以通过它访问完全限定的 Kotlin/Java 类型。例如,使用以下 skip.yml:
skip: mode: 'native' dynamicroot: 'D'你可以编写如下代码:
let date = try D.java.util.Date(999)let time: Int64 = date.time!这是如何工作的?Skip 的构建插件检测到你使用了 D.java.util.Date 并生成一个匹配的 AnyDynamicObject 子类。要访问静态成员,生成的子类包含一个 Companion(options:) 函数,镜像 Kotlin 对 Companion 用于静态成员的用法。
let date: AnyDynamicObject = try D.java.util.Date.Companion().parse(dateString)!因为 D.java.util.Date 是一个真实类型,你可以创建 typealias:
typealias JDate = D.java.util.Date
...
let d1 = JDate(999)let s: String? = try d1.instant?.toString()let d2: JDate? = try JDate.Companion().parse(dateString)如果你有一个基础 AnyDynamicObject 值的引用,想将其转换为生成的 dynamicroot 类型,使用 as() 函数:
let object: AnyDynamicObject = ...let date: D.java.util.Date = object.as(D.java.util.Date.self)
// or...
let date: JDate = object.as(JDate.self)使用 dynamicroot 时,请记住以下几点:
- 生成的类型具有
internal可见性,因此仅对当前模块可见。这防止了多个链接模块使用相同 Kotlin/Java 类型时发生冲突。 - 这些类型仅为代码的 Android 构建生成。毕竟 iOS 无法使用 Kotlin/Java API。所以你必须在用
#if os(Android)保护的代码中使用这些生成的类型。请参见跨平台主题。
结合 AnyDynamicObject 的桥接
Section titled “结合 AnyDynamicObject 的桥接”前面的章节描述了从原生 Swift 使用 Kotlin/Java 的两种方法:桥接转译的 Swift 代码或使用 AnyDynamicObject。这两种方法也可以结合使用。
假设你已经将 Kotlin API 封装在转译的 Swift 中用于桥接。你可能仍希望在某些高级用例中将底层 Kotlin 对象暴露给 Android 代码。Skip 的 Firebase 包将 iOS Firebase API 映射到 Android 时就经常使用这种模式。以下是代码摘录:
#if SKIPpublic final class FirebaseApp { // Allow code to access underlying Kotlin object if needed public let app: com.google.firebase.FirebaseApp
public init(app: com.google.firebase.FirebaseApp) { self.app = app }
public var name: String { app.name }
public var options: FirebaseOptions { FirebaseOptions(options: app.options) }
...}#endif但 com.google.firebase.FirebaseApp 类型将如何表示给原生 Swift?这是一个原生 Kotlin 类型,所以 Skip 对其 API 一无所知。答案是 Skip 会将其映射为 AnyDynamicObject。桥接后的 FirebaseApp 的 Swift 实现大致如下:
public final class FirebaseApp { public var app: AnyDynamicObject { // Delegate to Kotlin code ... }
public init(app: AnyDynamicObject) { // Delegate to Kotlin code ... }
public var name: String { // Delegate to Kotlin code ... }
...}你必须在任何应映射为 AnyDynamicObject 的 Swift API 上指定显式类型并使用完全限定的 Kotlin/Java 类名。以下转译的 Swift 将正确桥接为 AnyDynamicObject:
let app: com.google.firebase.FirebaseApp = ...
func f(app: com.google.firebase.FirebaseApp) { ...}但以下示例会产生构建时错误:
import com.google.firebase.FirebaseApp
let app: FirebaseApp = ... // Not fully qualifiedlet app = com.google.firebase.FirebaseApp() // No explicit type
func f(app: FirebaseApp) { // Not fully qualified ...}转译桥接和 AnyDynamicObject 的组合允许你为重要的 Kotlin 和 Java 服务提供自然的 Swift API,同时仍然为高级用例暴露原始的 Kotlin 或 Java 对象。
无论你使用原生还是转译模式,你都在用 Swift 编写代码。因此,在两者之间迁移比在不同编程语言之间切换容易得多——实际上通常是微不足道的。从转译模式迁移到原生模式尤其如此,因为转译模式仅支持原生模式 Swift 语法的子集,而原生模式提供了更多的第三方库。
在将项目切换到 Skip Fuse(原生模式)之前,请确保系统已安装必要的原生构建工具。运行以下命令安装 Swift SDK for Android 并验证环境:
skip android sdk installskip checkup --native要在 Skip Lite 和 Skip Fuse 之间切换整个应用,你必须更新 Package.swift 中的依赖和 skip.yml 文件中的配置。
1. 更新 Package.swift
Section titled “1. 更新 Package.swift”Skip Lite 应用通常依赖 skip-ui.git,而 Skip Fuse 应用依赖 skip-fuse-ui.git。你还必须将目标依赖从 SkipUI 更新为 SkipFuseUI。
Skip Lite(转译):
dependencies: [ .package(url: "https://github.com/skiptools/skip-ui.git", from: "1.0.0"), // ...],targets: [ .target(name: "MyApp", dependencies: [ .product(name: "SkipUI", package: "skip-ui") ], ...)]Skip Fuse(原生):
dependencies: [ .package(url: "https://github.com/skiptools/skip-fuse-ui.git", from: "1.0.0"), // ...],targets: [ .target(name: "MyApp", dependencies: [ .product(name: "SkipFuseUI", package: "skip-fuse-ui") ], ...)]2. 更新 skip.yml
Section titled “2. 更新 skip.yml”更改模块 Skip/skip.yml 文件中的 mode:
skip: mode: 'native' # change to 'transpiled' for Skip Lite3. 清理并重新构建
Section titled “3. 清理并重新构建”在模式之间切换会显著改变底层构建架构。进行这些更改后,你必须对项目执行清理并重新构建,以确保所有产物都为新模式正确生成:
- 在 Xcode 中:Product > Clean Build Folder(Command-Shift-K)。
- 如果遇到持续的构建问题,删除
Android/.gradle和Android/app/build目录。 - 重新运行构建。