Shaula Blog

美丽的东西都是肤浅的

Swift 6.1 Package Traits:让依赖管理更灵活

Swift 6.1 引入的 Package Traits 为 Swift Package Manager 带来了更灵活的条件编译和可选依赖管理能力。

为什么需要它?

实际开发中经常遇到这些问题:

  • 可插拔依赖:比如 Swift OpenAPI Generator 支持 URLSession、AsyncHTTPClient、Vapor 等多种传输层。为了不把全部依赖都打进二进制,往往要拆成多个仓库,维护和发现都麻烦。
  • 平台差异:Apple 平台用 OSLog,服务器端用 swift-log,需要一种优雅的方式来处理这种差异。
  • 实验性 API:想发布新功能但又不想承诺稳定 API,加下划线或特殊注解会影响代码补全,不够理想。

基本用法

Package.swift 中定义特性:

let package = Package(
    name: "MyLibrary",
    traits: [
        "Logging",
        .trait(name: "Networking", enabledTraits: ["Logging"]),
        .default(enabledTraits: ["Logging"])
    ],
)

上面定义了两个特性:LoggingNetworking。启用 Networking 时会自动启用 Logging,默认只开启 Logging

依赖时指定特性:

dependencies: [
    .package(url: "...", from: "1.0.0", traits: [.defaults, "Networking"]),
    // 禁用所有特性(包括默认)
    .package(url: "...", from: "1.0.0", traits: []),
]

也可以根据当前包的特性条件化开启依赖的特性:

dependencies: [
    .package(url: "...", from: "1.0.0", traits: [
        .trait(name: "AdvancedLogging", condition: .when(traits: ["Debug"]))
    ]),
]

实战示例:主题色 UI 库

// swift-tools-version: 6.3
import PackageDescription

let package = Package(
    name: "MyLibraryTest",
    products: [.library(name: "MyLibraryTest", targets: ["MyLibraryTest"])],
    traits: [
        .trait(name: "Red"),
        .trait(name: "White"),
        .trait(name: "Blue"),
        .trait(name: "Yellow"),
        .default(enabledTraits: ["Blue"])
    ],
    targets: [
        .target(
            name: "MyLibraryTest",
            cSettings: [
                .define("SHAULA_RED", .when(traits: ["Red"])),
                .define("SHAULA_WHITE", .when(traits: ["White"])),
                .define("SHAULA_BLUE", .when(traits: ["Blue"])),
                .define("SHAULA_YELLOW", .when(traits: ["Yellow"])),
            ]
        ),
    ]
)

客户端包可以按需覆盖默认主题:

dependencies: [
    .package(path: "../../", traits: ["Red", "Yellow"])
]

条件编译

在代码中用 #if 检查特性:

#if Blue
import BlueTheme
#endif

func applyTheme() {
    #if Red
    ThemeManager.apply(.red)
    #elseif Blue
    ThemeManager.apply(.blue)
    #else
    ThemeManager.apply(.default)
    #endif
}

命令行控制

swift build --traits Red,White      # 启用指定特性
swift build --enable-all-traits     # 启用所有
swift build --disable-default-traits # 禁用默认

几点建议

  • 特性应该是累加的:启用一个特性不要移除其他 API。如果确实有互斥需求,用编译时错误检查:#if Red && Blue #error("不能同时启用") #endif
  • 默认特性要谨慎:移除默认特性属于破坏性变更(SemVer major)。选大多数用户都会用的那个。
  • 命名规范:符合 Swift 标识符规则,别用 default/defaults。用 NetworkingCrypto 这类描述性名称。

小结

Package Traits 解决了长期困扰 Swift 包管理器的可选依赖和条件编译问题。合理使用可以让你的库更加灵活,为不同场景的用户提供最佳体验。