内部架构
本文档描述 Skip 的内部架构:Swift 源代码如何变成一个运行中的 Android 应用。它涵盖构建插件集成、skipstone 处理流水线、Gradle 项目生成、资源处理和应用启动序列。有关 Skip 两种模式的高级概述,请参见 Lite 与 Fuse 模式。
Skip 作为 SwiftPM 构建插件运行,集成到 Xcode 的构建系统中。当你构建项目时,Skip 处理依赖树中的每个 Swift 模块,生成一个包含 Kotlin 源代码的并行 Gradle 模块树。然后 Gradle 编译并将 Kotlin 打包为 Android 应用,与 iOS 一起在模拟器或设备上启动。
flowchart LR
A["Swift Source\n(SwiftPM Modules)"] --> B["Skip Build Plugin\n(skipstone)"]
B --> C["Kotlin Source\n(Gradle Modules)"]
C --> D["Gradle Build\n(Android SDK)"]
D --> E["Android App\n(.apk)"]
style B fill:#4A90D9,color:#fff
过程因每个模块的模式而异:
- Skip Lite 模块的 Swift 源代码由 Skip 转译器转译为 Kotlin。
- Skip Fuse 模块的 Swift 源代码使用 Swift SDK for Android ↗ 为 Android 原生编译,并自动生成到 Kotlin 的 JNI 桥接。
两种模式都生成参与同一 Android 构建的 Gradle 模块。
构建插件集成
Section titled “构建插件集成”Skip 的构建基础设施跨越两个仓库:
| 仓库 | 提供 |
|---|---|
| skipstone ↗ | skip 二进制命令行工具 |
| skip ↗ | skipstone SwiftPM 构建插件和测试框架 |
skip 仓库提供一个名为 skipstone 的 SwiftPM 构建工具插件。在发布构建中,此插件从 skipstone 仓库下载预构建的 skip 二进制文件。对于本地开发(当设置了 SKIPLOCAL 或工作目录以“skipstone”结尾时),它使用本地构建的版本。
插件如何集成到 Xcode
Section titled “插件如何集成到 Xcode”skipstone 插件实现了 SwiftPM 的 BuildToolPlugin ↗ 协议。在构建过程中,Xcode 为项目中的每个目标调用 createBuildCommands(context:target:)。插件执行以下操作:
- 扫描目标中的
Skip/skip.yml配置文件。没有此文件的目标将被排除。 - 解析依赖:遍历目标的依赖图,收集同样具有
skip.yml文件的对等模块。 - 创建符号链接:链接到依赖模块的插件输出目录,以便每个模块在 Gradle 编译期间可以引用其依赖。
- 发出构建命令:使用适当参数调用
skipCLI。
sequenceDiagram
participant Xcode
participant Plugin as skipstone Plugin
participant CLI as skip CLI
participant Gradle
Xcode->>Plugin: createBuildCommands(target)
Plugin->>Plugin: Scan for Skip/skip.yml
Plugin->>Plugin: Resolve module dependencies
Plugin->>Plugin: Create dependency symlinks
Plugin->>CLI: Invoke Skip CLI
CLI->>CLI: Transpile Swift → Kotlin (Lite)
CLI->>CLI: Bridge Swift → Kotlin (Fuse)
CLI->>CLI: Generate build.gradle.kts
CLI->>Gradle: Kotlin source + Gradle config
Gradle->>Gradle: Compile Kotlin + package APK
插件输出结构
Section titled “插件输出结构”插件将输出写入标准 SwiftPM 插件输出目录。确切路径因环境而异:
| 环境 | 输出路径 |
|---|---|
| Xcode | DerivedData/.../SourcePackages/plugins/<package>.output/<target>/skipstone/ |
| SwiftPM 5 CLI | .build/plugins/outputs/<package>/<target>/skipstone/ |
| SwiftPM 6 CLI | .build/plugins/outputs/<package>/<target>/destination/skipstone/ |
在每个模块的输出目录中,Skip 生成一个完整的 Gradle 模块:
文件夹ModuleName/
- build.gradle.kts 从 Package.swift + skip.yml 生成
文件夹src/
文件夹main/
文件夹kotlin/
文件夹module/
文件夹name/ Kotlin 包(如 skip.ui)
- TranspiledFile.kt 每个 .swift 源文件对应一个 .kt
文件夹assets/ 已处理资源(Android AssetManager)
- …
文件夹res/ Android 资源(字符串、值)
- …
文件夹test/
文件夹kotlin/ 转译的 XCTest 用例(JUnit)
- …
- .sourcehash 增量构建标记
Skip 使用 .sourcehash 标记文件跟踪源文件修改。每个模块的构建命令将其 .sourcehash 文件声明为输入(供依赖模块使用)和输出。这创建了一个依赖链,确保模块按正确顺序转译,并且仅在源代码发生变化时才进行。
skip.yml 配置
Section titled “skip.yml 配置”每个模块的 Skip/skip.yml 文件控制 Skip 如何处理该模块。此文件示例:
skip: mode: 'native' # Fuse 用 'native',Lite 省略或 'transpiled' bridging: true # 启用 public API 自动桥接(Fuse) resources: # 自定义资源路径 - path: CustomResources mode: copy # 'process'(默认)或 'copy'build: contents: - block: 'dependencies' # Android/Gradle 依赖 contents: - implementation("androidx.compose.material3:material3:1.2.0")skip 块控制 Skip 处理的行为和结构,settings 和 build 块允许自定义模块的输出 settings.gradle.kts 和 build.gradle.kts 文件。这最常用于向 Gradle 项目添加 Maven 风格依赖 ↗,以便与外部依赖集成。
有关 Gradle 配置的完整参考,请参见 Gradle 项目参考。
Skip 处理模式
Section titled “Skip 处理模式”所有 Skip 项目都包含一些转译的 Skip Lite 模块。例如,SkipUI 总是被转译为 Kotlin,无论顶层应用是编译的 Skip Fuse 还是转译的 Skip Lite 应用。在 Skip Fuse 的情况下,额外的原生 SkipFuseUI 模块处理 Swift 侧到 SkipUI 模块创建的转译 Skip Lite Jetpack Compose 代码的桥接。
更一般地说,Skip Fuse 模块可以依赖 Skip Lite 模块并通过桥接代码与之交互。这就是原生编译的应用可以集成 Skip 提供的各种平台框架(如 SkipAV、SkipNFC 和 SkipBluetooth)以及第三方集成模块(如 SkipFirebase、SkipSupabase 和 SkipAuth0)的方式。
模块的模式由 skip.yml 决定:
skip: mode: 'transpiled' | 'native' # 转译 Lite 或编译 Fuse 模式 bridging: true # 自动桥接所有 public API当 mode 设置为 native 时,Skip 还会检查依赖树中是否存在 SkipFuse。automatic 模式(默认)在 SkipFuse 存在且模块是主应用模块时选择原生模式。
Skip Lite:转译流水线
Section titled “Skip Lite:转译流水线”在 Skip Lite 模式下,转译器将 Swift 源代码转换为等效的 Kotlin 源代码。这是一个多阶段流水线,主要在 skipstone 仓库的 SkipSyntax 模块中实现。
flowchart TD
A["1. Parse\nSwiftSyntax → Syntax Trees"] --> B["2. Decode\nSyntax Trees → Statement/Expression AST"]
B --> C["3. Gather\nCollect types, functions, extensions\ninto CodebaseInfo"]
C --> D["4. Prepare\nResolve types, synthesize\nconstructors, process generics"]
D --> E["5. Translate\nSwift AST → Kotlin AST"]
E --> F["6. Transform\nTransformer plugins applied\nin sequence"]
F --> G["7. Output\nRender Kotlin source\nwith source mapping"]
style A fill:#6B7280,color:#fff
style B fill:#6B7280,color:#fff
style C fill:#4A90D9,color:#fff
style D fill:#4A90D9,color:#fff
style E fill:#7B2FBE,color:#fff
style F fill:#7B2FBE,color:#fff
style G fill:#059669,color:#fff
阶段 1–2:解析与解码
Section titled “阶段 1–2:解析与解码”Skip 使用标准 SwiftSyntax ↗ 库将 Swift 源文件解析为语法树。然后将这些语法树解码为 Skip 的内部 AST 表示——一棵由 Statement 和 Expression 节点组成的树,捕捉代码的语义结构。
多个文件使用 Swift 的结构化并发(withThrowingTaskGroup)并行解析。
阶段 3–4:收集与准备
Section titled “阶段 3–4:收集与准备”收集阶段遍历所有已解析的文件,构建 CodebaseInfo 对象——一个覆盖整个代码库的符号表,跟踪:
- 所有类型声明(类、结构体、枚举、协议、Actor)
- 扩展声明及其关联类型
- 顶层函数和变量
- 类型别名
- 依赖模块的导出符号
每个转换器还有一个 gather() 方法,在此阶段运行以收集转换器特定的信息。
准备阶段(prepareForUse())解析类型引用、合成隐式构造函数并处理泛型。这为翻译阶段提供了代码库类型系统的完整视图。
阶段 5:翻译
Section titled “阶段 5:翻译”KotlinTranslator 将每个 Swift AST 节点转换为其 Kotlin 等效形式,生成 KotlinSyntaxTree。这包括:
- 模块名映射:Swift 模块名按
CamelCase→lower.dot.separated的约定转换为 Kotlin 包名(如SkipFoundation→skip.foundation)。可以在skip.yml中指定自定义映射。 - 导入解析:Swift 导入被映射到 Kotlin 等效形式,引用 SkipLib、SkipFoundation 和 SkipUI 等框架模块。
- 语句和表达式翻译:每个 Swift 结构被映射到其 Kotlin 对应物。
阶段 6:转换
Section titled “阶段 6:转换”初始翻译后,约 20 个 KotlinTransformer 实现序列对 Kotlin AST 进行精化。顺序很重要——每个转换器可能依赖前面转换器的更改。这些转换器处理 Swift 和 Kotlin 语义之间的根本差异:
| 转换器 | 用途 |
|---|---|
| EscapeKeywords | 转义与 Kotlin 硬关键字冲突的标识符 |
| OptionSet | 在 Kotlin 中实现 Swift 的 OptionSet 协议约定 |
| Struct | 为结构体添加复制语义、变更跟踪(willmutate/didmutate)和成员初始化器 |
| CommonProtocols | 移除 Kotlin 中不需要的协议一致性 |
| Codable | 为 Codable 类型生成 encode/decode 实现 |
| RawRepresentable | 为 RawRepresentable 类型添加工厂函数 |
| Enum | 将枚举转换为 Kotlin 密封类,合成 CaseIterable |
| ConstructorAndSideEffectSuppression | 管理构造函数合成和副作用抑制 |
| ErrorToThrowable | 将 Swift 的 Error 协议映射到 Kotlin 的 Throwable |
| Observation | 转换 @Observable 属性以集成 Compose 状态 |
| IfWhen | 将 if/else 链转换为 Kotlin when 表达式 |
| Defer | 实现 Swift 的 defer 语句 |
| DisambiguateFunctions | 解析重载函数的歧义 |
| TupleLabel | 处理元组标签语义 |
| Concurrency | 转换 async/await、Task 和结构化并发 |
| SwiftUI | 将 SwiftUI 视图和修饰符转换为 Jetpack Compose |
| Imports | 解析和生成 Kotlin import 语句 |
| UnitTest | 将 XCTest 断言转换为 JUnit 等效形式 |
| Bundle | 处理资源包引用 |
| FoundationBridge | 桥接 Foundation 框架调用 |
| Bridge | 生成双向 Swift-Kotlin 互操作代码(启用桥接时) |
SwiftUI 转换器将 SwiftUI 视图声明、修饰符和状态管理转换为 Jetpack Compose 等效形式。这正是 SkipUI 能够从 SwiftUI 代码渲染原生 Android UI 的原因。
Struct 转换器确保 Swift 的值类型语义(写时复制、变更跟踪)在 Kotlin 的引用类型世界中得到忠实复制。
阶段 7:输出
Section titled “阶段 7:输出”OutputGenerator 将 Kotlin AST 渲染为源文件文本,每个 .swift 输入文件生成一个 .kt 文件。在渲染过程中,它构建一个 OutputMap,记录生成的 Kotlin 与原始 Swift 源代码之间的字节偏移映射。
这些源码映射被测试框架用于将 Kotlin 堆栈跟踪映射回 Swift 文件和行位置,使测试失败和运行时错误在 Xcode 中可以直接定位。
转译器将 Swift 类型映射到其 Kotlin/Skip 等效形式:
flowchart LR
subgraph Swift
S1["Int"]
S2["String"]
S3["Array<T>"]
S4["Dictionary<K,V>"]
S5["Optional<T>"]
S6["struct"]
S7["enum"]
end
subgraph Kotlin
K1["Int / Long"]
K2["String"]
K3["skip.lib.Array<T>"]
K4["skip.lib.Dictionary<K,V>"]
K5["T?"]
K6["class + copy semantics"]
K7["sealed class"]
end
S1 --> K1
S2 --> K2
S3 --> K3
S4 --> K4
S5 --> K5
S6 --> K6
S7 --> K7
注意,Swift 的 Int(64 位)在 Skip Lite 中映射为 Kotlin 的 Int(32 位)。这是一个已知的静默溢出 bug 来源——详见 Swift 支持 中的数值处理详情。
Array 和 Dictionary 等集合类型使用 SkipLib 实现(skip.lib.Array、skip.lib.Dictionary),而不是 Kotlin 的标准库集合,因为这些实现保留了 Swift 的值类型复制语义。
Skip Fuse:原生编译流水线
Section titled “Skip Fuse:原生编译流水线”在 Skip Fuse 的 native 模式下,模块的 Swift 源代码使用官方 Swift SDK for Android(Swift 6.3+)为 Android 原生编译。转译器仍然参与其中,但其角色从完整转译变为桥接生成。
flowchart TD
subgraph "Swift Side"
A["Swift Source"] --> B["Swift SDK for Android\n(swiftc cross-compilation)"]
B --> C["Native .so libraries"]
end
subgraph "Skip Processing"
A --> D["Bridge Generator\n(skipstone)"]
D --> E["Kotlin Bridge Wrappers\n(.kt files)"]
D --> F["Swift Bridge Support\n(_Bridge.swift)"]
end
subgraph "Android Build"
E --> G["Gradle / Kotlin Compiler"]
C --> G
F --> B
G --> H["Android App\n(.apk)"]
end
style D fill:#7B2FBE,color:#fff
style B fill:#4A90D9,color:#fff
style G fill:#059669,color:#fff
Fuse 与 Lite 的区别
Section titled “Fuse 与 Lite 的区别”在 Fuse 模式下,skip skipstone 命令将模块的 Swift 文件视为桥接文件而非转译文件。它不是逐行将 Swift 转换为 Kotlin,而是:
- 分析 Swift API 表面(public 类型、方法、属性)。
- 生成 Kotlin 桥接包装器,通过 JNI 调用原生 Swift。
- 生成 Swift 桥接支持文件(
_Bridge.swift),将 Swift 符号暴露给 JNI 层。 - 生成 Gradle 模块,包含 Kotlin 包装器和对原生
.so库的引用。
桥接系统是双向的,支持 Swift 到 Kotlin 和 Kotlin 到 Swift 的调用:
flowchart LR
subgraph "Native Swift (Android)"
SW["Swift Code"]
SB["_Bridge.swift\n(JNI exports)"]
end
subgraph "JNI Layer"
JNI["Java Native Interface"]
end
subgraph "Kotlin/JVM"
KB["Bridge Wrappers\n(generated .kt)"]
KC["Kotlin/Java Code"]
end
SW <--> SB
SB <--> JNI
JNI <--> KB
KB <--> KC
两个专门的访问器生成桥接代码:
KotlinBridgeToSwiftVisitor—— 生成 Kotlin 包装类,通过 JNI 委托方法调用到原生 Swift。KotlinBridgeToKotlinVisitor—— 为需要从 Kotlin 侧访问的类型生成纯 Kotlin 代码。
桥接生成可以在不同粒度上配置——详见桥接中的逐项(@bridge)、逐类型(@bridgeMembers)和模块级(bridging: true)配置。
Fuse 依赖
Section titled “Fuse 依赖”Skip Fuse 应用依赖一系列基础设施模块:
flowchart BT
A["swift-jni\n(JNI C headers + Swift wrapper)"] --> B["skip-android-bridge\n(Android-specific bridging)"]
B --> C["skip-fuse\n(OSLog, Observable,\nAnyDynamicObject)"]
C --> D["skip-fuse-ui\n(Native Swift UI on Android)"]
D --> E["Your Fuse App"]
style A fill:#6B7280,color:#fff
style B fill:#6B7280,color:#fff
style C fill:#4A90D9,color:#fff
style D fill:#7B2FBE,color:#fff
SkipFuse 模块提供运行时支持,包括与 Jetpack Compose 的 @Observable 状态同步。在 Fuse 模块中定义 @Observable 类型的任何 Swift 文件必须 import SkipFuse,Android UI 更新才能正常工作。
Gradle 项目生成
Section titled “Gradle 项目生成”Skip 自动生成一个完整的 Gradle 项目结构,镜像你的 SwiftPM 依赖树。SkipBuild 模块中的 GradleProject 系统处理此转换。
从 Package.swift 到 build.gradle.kts
Section titled “从 Package.swift 到 build.gradle.kts”对于每个带有 skip.yml 文件的 Swift 模块,Skip 生成一个 build.gradle.kts 文件,包括:
- 插件:Android library 或 application 插件、Kotlin 插件、Compose 编译器插件
- 依赖:模块间依赖(通过项目引用)和外部 Gradle 依赖(来自
skip.yml) - 源集:指向生成的 Kotlin 源目录
- Android 配置:最低/目标 SDK 版本、Compose 设置、ProGuard 规则
生成的 Gradle 块使用树形结构的 GradleBlock 表示,支持合并——来自 skip.yml 配置的同名块与生成的默认值合并,允许细粒度自定义。
除了每个模块的 build.gradle.kts 文件,Skip 还生成:
| 文件 | 用途 |
|---|---|
settings.gradle.kts | 模块包含、插件管理、桥接模块列表 |
gradle.properties | JVM 参数、AndroidX 标志、来自 skip.yml 的自定义属性 |
gradle/wrapper/gradle-wrapper.properties | Gradle 版本锁定 |
对于应用项目,顶层 Android/ 目录包含可手动编辑的 Gradle 配置,其中包含生成的模块。完整结构请参见 Gradle 项目参考。
Skip 创建一个镜像 SwiftPM 依赖树的 Gradle 依赖树:
flowchart TD
subgraph "Gradle Modules"
GA["your.app"] --> GM["your.model"]
GA --> GUI["skip.ui"]
GM --> GF["skip.foundation"]
GUI --> GF
GF --> GL["skip.lib"]
end
subgraph "SwiftPM Modules"
YA["YourApp"] --> YM["YourModel"]
YA --> SUI["SkipUI"]
YM --> SF["SkipFoundation"]
SUI --> SF
SF --> SL["SkipLib"]
end
YA -.->|"generates"| GA
YM -.->|"generates"| GM
style YA fill:#4A90D9,color:#fff
style GA fill:#059669,color:#fff
style YM fill:#4A90D9,color:#fff
style GM fill:#059669,color:#fff
每个框架模块——SkipLib、SkipFoundation、SkipModel、SkipUI 和 SkipUnit——由插件处理并通过符号链接链接到 Gradle 树中。
Skip 处理来自 Swift 模块的资源,并通过 Gradle 的 asset 和 resource 系统使其可用于 Android 构建。
资源通过以下方式定位:
- 默认:模块源文件夹中的
Resources/目录。 - 显式配置:在
skip.yml的skip.resources下指定的路径。
| 模式 | 行为 | 用例 |
|---|---|---|
| Process(默认) | 扁平化目录层次结构;将 .xcstrings 转换为 Android strings.xml | 标准应用资源、可本地化字符串 |
| Copy | 保持目录层次结构不变 | 预结构化资源、自定义文件布局 |
处理后的资源输出到 src/main/assets/<package>/<name>/,通过 Android 的 AssetManager 访问,或输出到 src/main/res/ 用于本地化字符串等 Android 资源类型。
可本地化字符串
Section titled “可本地化字符串”Xcode 的 .xcstrings 文件(字符串目录)在处理阶段自动转换为 Android 的 values/strings.xml 格式。这允许一套本地化文件同时服务两个平台。
Skip 不是复制资源文件,而是从 Gradle 输出目录创建符号链接回到原始源文件。这意味着对资源的编辑会立即反映在下一次 Android 构建中,无需重新转译。只读资源(来自依赖)则被复制。
Skip 与 Xcode 的测试运行器集成,在 JVM 或 Android 上执行转译或编译的测试。测试基础设施由 skip ↗ 仓库中的 SkipTest 和 SkipDrive 模块提供。
Skip Lite 测试流程
Section titled “Skip Lite 测试流程”sequenceDiagram
participant Xcode
participant XCTest as XCSkipTests
participant Gradle
participant JVM as JVM / Robolectric
Xcode->>XCTest: Run test target
XCTest->>Gradle: Execute testDebug / connectedAndroidTest
Gradle->>JVM: Run transpiled JUnit tests
JVM-->>Gradle: JUnit XML results
Gradle-->>XCTest: Parse test results
XCTest-->>Xcode: Report as XCTest failures
Lite 测试目标中的每个 XCTestCase 子类都会由 SkipUnit 转换器自动转译为 Kotlin/JUnit 测试类。XCSkipTests 框架(如果不存在则自动生成)协调执行:
- Robolectric(默认,无需设备):使用 Robolectric 在本地 JVM 上运行转译的测试,模拟 Android API。通过 Gradle 的
testDebug动作调用。 - Instrumented(设置
ANDROID_SERIAL):通过connectedDebugAndroidTest在真实 Android 设备或模拟器上部署和运行测试。
Skip Fuse 测试流程
Section titled “Skip Fuse 测试流程”Fuse 测试被交叉编译为 Android 原生 Swift 并通过 adb 执行:
- CLI 模式(
skip android test):将裸可执行文件推送到设备。支持资源包但不支持 Android 框架 API。 - APK 模式(
skip android test --apk):将测试打包在 APK 中,具有完整的 JNI 和 Android 框架访问权限。
完整测试指南请参见测试。
当 Kotlin 测试失败或发生运行时错误时,SkipDrive 中的 GradleDriver 逐行解析 Gradle 输出,提取 Kotlin 文件路径和行号。然后使用输出阶段生成的源码映射将这些翻译回原始 Swift 源位置,使失败在 Xcode 中显示在正确的 Swift 文件和行处。
应用构建与启动
Section titled “应用构建与启动”当你在 Xcode 中为 Skip 项目按下运行时,会执行以下序列:
sequenceDiagram
participant Dev as Developer
participant Xcode
participant Plugin as skipstone Plugin
participant CLI as skip CLI
participant Gradle
participant Emulator as Android Emulator
Dev->>Xcode: Press Run (⌘R)
Xcode->>Xcode: Build iOS target normally
par iOS Build
Xcode->>Xcode: Compile Swift for iOS
Xcode->>Xcode: Link and sign iOS app
Xcode->>Xcode: Launch on iOS Simulator
and Android Build
Xcode->>Plugin: Build plugin targets
Plugin->>CLI: skip skipstone (per module)
CLI->>CLI: Transpile/bridge Swift → Kotlin
CLI->>CLI: Generate Gradle files
CLI-->>Plugin: .sourcehash markers
Plugin-->>Xcode: Build commands complete
Xcode->>Gradle: Build Android project
Gradle->>Gradle: Compile Kotlin
Gradle->>Gradle: Package APK
Gradle->>Emulator: Install and launch APK
end
iOS 应用的 .xcconfig 文件通过 SKIP_ACTION 设置控制 Android 构建行为:
| 值 | 行为 |
|---|---|
launch(默认) | 构建并在 Android 模拟器/设备上启动 |
build | 构建 APK 但不启动 |
none | 完全跳过 Android 构建(仅 iOS 迭代) |
有关特定主题的更多详情:
- Lite 与 Fuse 模式 —— 在转译和原生编译之间选择
- Gradle 项目参考 —— 详细的 Gradle 结构和配置
- 桥接 —— Fuse 模式下的 Swift-Kotlin 互操作
- 测试 —— 在 Android 上运行测试
- 部署 —— 导出和分发应用
- 跨平台主题 —— 使用
#if SKIP和#if os(Android)编写平台特定代码 - Swift 支持 —— Skip Lite 中支持的 Swift 语言特性