跳转到内容
Skip 中文站
3.2k

项目结构

Skip 应用项目是通过 skip init 或 skip create 创建的项目。本质上,它是一个标准的 Swift Package Manager 项目 ↗,包含顶层的 Package.swift 以及 Sources/ 和 Tests/ 目录。

Skip 在此基础上增加了共享的 Skip.env 元数据,以及 Darwin/ 和 Android/ 子文件夹,分别容纳各自平台的构建系统以及平台特定的元数据和部署信息。

Skip 项目可以使用 swift build 本地构建,使用 swift test 测试。此外,Skip 特有的命令如 skip export 可用于构建平台特定的二进制文件(iOS 的 .ipa 和 Android 的 .apk),以便通过各种应用商店分发。

有四个标准示例可以参考,它们展示了标准 Skip 项目的结构:

  1. HelloSkip:转译的 Skip Lite 应用项目基础示例。
  2. HowdySkip:编译的 Skip Fuse 应用项目基础示例。
  3. AhoySkipper:多模块 Skip Fuse 应用项目。
  4. HiyaSkip:使用编译(Skip Fuse)UI 模块桥接到转译(Skip Lite)模型层模块的 Skip 应用项目示例。

以一个包含两个模块和测试用例的“Ahoy Skipper”Skip Fuse 应用为例,可以通过以下 skip init 命令(或等效的交互式 skip create 命令)生成:

Terminal window
skip init --native-app --bridged --module-tests --show-tree --appid=ahoy.skipper ahoy-skipper AhoySkipper SkipperModel

应用的精简文件系统布局如下所示:

此文件是 Swift Package Manager (SwiftPM) 的清单文件。它定义了项目名称、产品(库或可执行文件)、依赖和目标。在 Skip 项目中,它还声明了 skipstone 插件,该插件负责将 iOS 项目转换为等效的 Android 项目。

此文件是 Xcode 工作区,用于将多个 Xcode 项目和 Swift 包组织在一起的容器。在 Skip 中,它允许你在单个界面中管理主要的 Darwin 应用项目和共享 Swift 模块,确保共享代码的更改立即反映在应用构建中。

生成的 Project.xcworkspace 仅包含 Darwin/ProjectName.xcodeproj,但可以按贡献指南中的描述添加额外的项目和框架。

这是 Skip 项目的共享配置环境文件。它包含项目范围的元数据键值对,如 PRODUCT_NAME、MARKETING_VERSION 和 PRODUCT_BUNDLE_IDENTIFIER。这些变量被 Xcode 构建过程和 Gradle 构建过程共同使用,以确保 iOS 和 Android 二进制文件之间的一致性。它被 Darwin/ProjectName.xcconfig 和 Android/settings.gradle.kts 文件共同引用。

共享应用入口点,负责启动和配置应用,持有主要的 ContentView.swift 并包含应用生命周期回调。这将是 Darwin Main.swift 和 Android Main.kt 共同调用的通用应用逻辑。

此文件是一个 SwiftUI View 文件,表示应用的主要用户界面。在 Skip 项目中,此文件中的 SwiftUI 代码将被转换为 Jetpack Compose 用于 Android,允许单个声明式 UI 定义驱动两个平台。

这是 Skip 特有的 YAML 配置文件。它控制模块级设置,如 Skip 模式、桥接配置和 Android 特定依赖。

Sources/<Module>/Resources/Module.xcassets

Section titled “Sources/<Module>/Resources/Module.xcassets”

这是位于特定 Swift 模块中的资源目录。它存放该模块范围内的图片、数据和本地化字符串等资源。Skip 确保这些资源被处理并提供给 iOS 包和 Android res 目录。

Sources/<Module>/Resources/Localizable.xcstrings

Section titled “Sources/<Module>/Resources/Localizable.xcstrings”

这是用于管理翻译的字符串目录文件。它是 Darwin 本地化的现代格式。Skip 在构建过程中将这些字符串转换为 Android 资源,从而在两个平台上提供翻译一致性。

此文件包含应用的业务逻辑和状态,通常使用 @Observable 宏。

此文件包含用于验证模块逻辑的测试用例。对于 Skip Fuse 项目,这些测试可以直接使用 skip android test 运行;对于 Skip Lite 项目,测试将被转译为 Kotlin,可以使用 skip test 运行。

这是 Xcode 构建配置文件。它提供了一种以纯文本格式定义构建设置(如编译器标志或搜索路径)的方式,而不是在不透明的 .xcodeproj 文件中管理,从而更容易管理环境特定的设置。

此配置文件会自动包含根级别的 Skip.env 文件,因此 Darwin 和 Android 项目可以共享通用属性。

这是 Xcode 项目文件,一个包含 Darwin(iOS/macOS)版本应用的配置、构建目标和文件引用的包。它是面向苹果生态系统的开发者的主要入口点。

此项目将自动被生成的 Project.xcworkspace 包含。

这是 Darwin 应用的主要资源目录。它通常包含 iOS 目标所需的应用图标集、启动图片和全局颜色资源。

Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon.png

Section titled “Darwin/Assets.xcassets/AppIcon.appiconset/AppIcon.png”

这是 Darwin 应用图标的源图片文件。

这是一个 XML 属性列表,定义了 iOS 应用的能力和权限,如推送通知或应用权限。

此文件是 iOS 应用的信息属性列表。它包含重要的配置数据,如包标识符、支持的方向和所需的硬件功能。

此文件作为 Darwin 入口点。它包含 @main 属性,用于初始化 iOS/macOS 版本的 SwiftUI App 生命周期。

此文件的内容会非常简洁。与其 Kotlin Main.kt 对应文件一样,它主要调用共享的 <Module>App.swift。

此目录包含 Fastlane 配置文件(Deliverfile、Fastfile、Appfile),用于自动化 iOS 应用的部署流程,特别是上传到 Apple App Store 和 TestFlight。

这是 Android 入口点。它包含 MainActivity 类,作为 Android 应用的主要 Activity。此文件的内容会非常简洁。与其 Swift Main.swift 对应文件一样,它主要调用共享的 <Module>App.swift。

这是一个用 Kotlin DSL 编写的 Gradle Settings 文件。它定义了项目级设置并包含构成 Android 构建的子模块(如 :app)。

这是模块级 Gradle 构建文件。它定义了 Android 特定的构建配置,包括 SDK 版本、签名配置和依赖(包括转译的 Swift 模块)。

此文件包含 R8/ProGuard 规则,用于代码缩减和优化。它确保 Kotlin 代码及其依赖在生产环境中被正确压缩。

这是 Android Manifest 文件。它向 Android 构建工具、Android OS 和 Google Play 描述应用的基本信息,包括权限和入口 Activity。

此文件夹包含各种密度 Android 屏幕的应用图标。

此目录包含 Fastlane 配置文件(Appfile、Fastfile),用于自动化 Android 应用的部署流程,特别是上传到 Google Play Store。

Skip 的快速入门指南演示了使用 skip create 交互式生成基础应用项目,但还有多种其他项目类型。本文档讨论了可以用 Skip 创建和构建的各种不同项目类型。

skip init 命令是交互式 skip create 命令的替代方案,可以通过单个非交互命令初始化应用。运行 skip init --help 或查看命令行参考获取 skip init 选项的完整列表。

使用以下命令创建新的双平台应用项目:

Terminal window
skip init --native-app --appid=bundle.id project-name AppName

传入 --open-xcode 参数可以立即在 Xcode 中打开项目。例如:

Terminal window
skip init --open-xcode --native-app --appid=bundle.id.HelloSkip hello-skip HelloSkip

这将创建一个 hello-skip/ 文件夹,包含一个新的 SwiftPM 包,其中有一个名为 HelloSkip 的模块,以及名为 Darwin 和 Android 的文件夹和共享的 Skip.env 应用配置文件。Darwin 文件夹将包含一个带有 HelloSkip 目标的 HelloSkip.xcodeproj 项目,你可以在 Xcode 中打开它。

Skip 的设计支持并鼓励使用多模块项目。你可以在 skip init 命令链末尾指定额外的模块名来创建模块化项目。例如:

Terminal window
skip init --native-app --appid=bundle.id.HelloSkip multi-project HelloSkip HelloModel HelloCore

此命令将创建一个包含三个模块的 SwiftPM 项目:HelloSkip、HelloModel 和 HelloCore。这种模块创建的启发式规则是,所有模块都依赖于其后续的兄弟模块,第一个模块(HelloSkip)初始依赖 SkipFuseUI,第二个模块依赖 SkipFuse 和 SkipModel。Package.swift 文件可以手动编辑以调整依赖,或添加对新框架的依赖。

你可以选择使用双平台框架共享功能,但创建独立的 iOS 和 Android 应用。例如,一些开发团队希望共享通用的模型和业务逻辑层,但为每个平台单独编写 UI。

Travel Posters 示例应用提供了这种模式的例子。你可以在这篇博客文章中了解更多信息。它具有以下顶层条目:

  • travel-posters-model:此 SwiftPM 包构建一个双平台框架,包含 iOS 和 Android 应用的通用模型层。Skip 确保你用 Swift 编写的 @Observable 类型不仅能驱动 SwiftUI 界面,还能驱动 Compose 界面。详见开发文档。
  • iOS:包含 TravelPosters iOS 应用和 Xcode 项目的目录,已将 travel-posters-model 作为包依赖。
  • Android:包含应用 Android 版本的目录。Android/lib 目录包含 travel-posters-model 及其依赖的各种 Skip 框架的导出归档。
  • TravelPostersNative.xcworkspace:包含 iOS 应用和 travel-posters-model 包的工作区。

使用 TravelPostersNative.xcworkspace 来迭代 iOS 应用和/或共享模型层。要将最新的 travel-posters-model 代码提供给 Android 应用:

Terminal window
skip export --project travel-posters-model -d Android/lib/debug/ --debug
skip export --project travel-posters-model -d Android/lib/release/ --release

有多种方法可以自动化此过程,从简单脚本到 git 子模块,再到将 Skip 生成的 Android travel-posters-model 输出发布到本地 Maven 仓库。使用最适合你团队工作流的系统。

其他注意事项:

  • 更新导出的库后,你可能需要在 Android Studio 中执行“Sync Project with Gradle Files”。
  • 使用具有对额外 Android 库的传递依赖的导出库函数可能导致运行时错误。你必须确保所有传递依赖都在你自己的应用的 build.gradle.kts 中。

SkipFuse 应用可以依赖纯 SwiftPM 包以及纯 Kotlin/Java Android 库。详见依赖章节。本节描述如何创建一个 Swift 框架,使其 API 可以同时从 Swift 和 Kotlin 中使用。最常见的用例是为 SkipLite 转译应用或独立的 iOS 和 Android 应用提供支持。

双平台 Skip 框架是封装通用功能的纯 SwiftPM 包。框架比应用项目更简单,因为它们不需要 Darwin/ 和 Android/ 文件夹。

创建 Skip 框架项目的界面截图

可以使用以下命令生成新的框架项目:

Terminal window
skip init --native-model lib-name ModuleName

这将创建一个新的 lib-name 文件夹,包含一个 Package.swift,其中有 ModuleName 和 ModuleNameTests 目标。

生成的包可以在 Xcode 中打开,你可以用它来构建和运行单元测试。或者在持续集成过程中使用终端的 swift build 和 swift test 进行无头测试。

传递给 skip init 的 --native-model 选项将配置 Skip 自动将我们模型的 public API 从编译的 Swift 桥接到 Android 的 ART ↗ Java 运行时。这是通过每个 Skip 模块中包含的 skip.yml 配置文件完成的。详见桥接和 skip.yml 的文档。

Skip 框架的结构与其他 SwiftPM 包完全相同:

  • 文件夹lib-name
    • Package.swift
    • README.md
    • 文件夹Sources
      • 文件夹ModuleName
        • ModuleName.swift
        • 文件夹Resources
          • Localizable.xcstrings
        • 文件夹Skip
          • skip.yml
    • 文件夹Tests
      • 文件夹ModuleNameTests
        • ModuleNameTests.swift
        • XCSkipTests.swift
        • 文件夹Resources
          • TestData.json
        • 文件夹Skip
          • skip.yml

Skip 框架使用标准的 Package.swift 文件,区别在于添加了对 Skip 库的依赖以及使用 skipstone 插件进行转译:

Package.swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "lib-name",
defaultLocalization: "en",
platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)],
products: [
.library(name: "ModuleName", targets: ["ModuleName"]),
],
dependencies: [
.package(url: "https://github.com/skiptools/skip.git", from: "1.7.0"),
.package(url: "https://github.com/skiptools/skip-fuse.git", from: "1.0.0"),
.package(url: "https://github.com/skiptools/skip-model.git", from: "1.0.0"),
],
targets: [
.target(name: "ModuleName", , dependencies: [
.product(name: "SkipFuse", package: "skip-fuse")
.product(name: "SkipModel", package: "skip-model")
], plugins: [
.plugin(name: "skipstone", package: "skip")
]),
.testTarget(name: "ModuleNameTests", dependencies: ["ModuleName"], plugins: [.plugin(name: "skipstone", package: "skip")]),
]
)

此配置包含了编写通用模型代码所需的依赖,包括能同时与 SwiftUI 和 Compose 用户界面配合的 @Observable。你可以根据特定需求自由编辑 Package.swift。


将现有应用迁移到 Skip 并非易事。大多数应用包含许多仅限 iOS 的依赖,这使得 Android 移植具有挑战性。

此外,当你使用 skip create 或 skip init 创建新的 Skip 应用时,它会处理使应用能够同时构建 iOS 和 Android 版本的所有复杂细节。这个过程足够复杂,我们不建议尝试迁移现有的 Xcode 项目。相反,选择以下两种方案之一来创建现有应用的 Android 版本:

  1. 使用 skip create 或 skip init 创建新的 Skip 应用,然后添加现有应用的依赖和代码。
  2. 保留现有的 Xcode 应用,使用 Android Studio 或你选择的 IDE 创建单独的 Android 应用。分别管理应用,但通过创建双平台框架共享代码。

无论你选择哪种方案,第一步都是相同的:

  1. 如果尚未模块化,将应用拆分为 Swift Package Manager 包。
  2. 从“基础”模块开始,逐步向上,尝试让每个模块都能为 Android 构建。

我们建议尽可能使用 Skip Fuse,因为它与现有 Swift 代码和依赖的兼容性更好。

将应用移植到全新平台并不容易,即使有 Skip 的帮助。请记住,我们随时愿意提供帮助。