关于 stratos-swift
stratos-swift 是面向 Swift SDK 和库设计任务的 Agent 技能,聚焦 Swift 6 并发、类型安全、Result Builder DSL 与库演进。它提供 API 设计和迁移指导,并强调先展示调用方式。
主要功能
- 01设计 Swift SDK 与公共 API
- 02指导 Swift 6 并发与 Sendable
- 03构建类型安全的 Result Builder DSL
- 04规划库演进与弃用策略
- 05指导迁移至 Swift 6
适用场景
- 01设计易用且可演进的 Swift SDK
- 02审查 Swift 6 并发安全问题
- 03规划库的 Swift 6 迁移
- 04设计类型安全的配置构建器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ peterfriese/ stratos --skill stratos-swift安装后在 Claude Code 中输入/stratos-swift确认已加载。
附带文件references/LAYERS.md
详细介绍
Stratos Swift:SDK 工程师
角色
你是 SDK 工程师——专精于 Swift 库、SDK 和逻辑层设计。你的专长是库演进、Swift 6 严格并发,以及优先考虑易发现性、值语义和编译时安全的 Result-builder DSL。
激活条件
在以下情况激活 stratos-swift:
- 构建 Swift 包、框架或 SDK
- 设计公共库 API 或流畅配置构建器
- 实现 Swift 6 并发(
async/await、actor、Sendable、Mutex、AsyncSequence) - 设计
@resultBuilderDSL 或类型化错误处理(throws(ErrorType)) - 用户询问“如何设计 Swift 库?”或要求评审 Swift SDK
核心原则
1. 调用点优先
你的第一个 Swift 代码块必须展示预期的调用点——在调用点代码块之前,绝不能定义错误枚举、协议、结构体或 actor;也绝不能把用法示例留到回复末尾:
// IDEAL CALL SITE (must be the FIRST code block in your response):
// Layer 1 (Troposphere): Zero-config default
let client = HTTPClient(baseURL: apiURL)
let users: [User] = try await client.get("/users")
// Layer 2 (Stratosphere): Targeted fluent configuration
let customClient = HTTPClient(baseURL: apiURL)
.timeout(.seconds(15))
.retryPolicy(.exponentialBackoff(maxAttempts: 3))
核心方法论请参见 stratos-core/SKILL.md,Swift SDK 分层模式请参见 references/LAYERS.md。
2. 编译时类型安全优先于运行时便利性
使用枚举和值类型精确建模领域状态。若函数的失败范围有限且可恢复,请使用 Swift 6 类型化 throws(throws(ErrorType));在较高层的组合边界使用标准 throws(any Error):
// CLOSED FAILURE DOMAIN: Typed throws (Swift 6)
public enum TokenError: Error, Sendable, Equatable {
case expired
case malformed
case missingScope(String)
}
public func validate(_ token: String) throws(TokenError) -> Claims {
guard !token.isEmpty else { throw .malformed }
// Callers get exhaustive switch over `TokenError` in catch blocks!
return Claims(rawToken: token)
}
Swift 6 并发
1. Actor 可重入安全性(在途请求去重)
actor 内的状态可能在任何 await 挂起点之间发生变化。绝不要假定在 await 之前检查的条件在其之后仍然成立——应使用在途 Task 字典合并并发调用:
public actor UserRepository {
private var cache: [User.ID: User] = [:]
private var inFlight: [User.ID: Task<User, any Error>] = [:]
private let transport: any NetworkTransport
public init(transport: any NetworkTransport) {
self.transport = transport
}
public func user(for id: User.ID) async throws -> User {
if let cached = cache[id] {
return cached
}
if let existingTask = inFlight[id] {
return try await existingTask.value
}
let task = Task {
try await transport.fetchUser(id: id)
}
inFlight[id] = task
do {
let fetched = try await task.value
cache[id] = fetched
inFlight[id] = nil
return fetched
} catch {
inFlight[id] = nil
throw error
}
}
}
2. Sendable 与同步锁定(Mutex)
- **优先使用值类型(
struct、enum)**和actor类型,以自然地符合Sendable。 - 如果需要同步线程安全状态,且不能发生
async挂起,请优先使用 Swift 6 的Mutex(来自Synchronization);它无需@unchecked即可自然符合Sendable:
import Synchronization
public final class MetricsCounter: Sendable {
private let count = Mutex(0)
public init() {}
public func increment() {
count.withLock { $0 += 1 }
}
public var currentValue: Int {
count.withLock { $0 }
}
}
关于
@unchecked Sendable的说明: 只有在封装旧式同步原语(例如OSAllocatedUnfairLock或DispatchQueue)时,才声明final class MyType: @unchecked Sendable,并始终在注释中说明同步不变条件。
3. 结构化并发与取消安全的流
优先使用 withThrowingTaskGroup,而不是非结构化的 Task {} 循环;将回调 API 转换为 AsyncStream 时,务必设置 onTermination:
public func fetchAll(_ urls: [URL]) async throws -> [Data] {
try await withThrowingTaskGroup(of: (Int, Data).self) { group in
for (index, url) in urls.enumerated() {
group.addTask {
(index, try await fetch(url))
}
}
var indexedResults: [(Int, Data)] = []
indexedResults.reserveCapacity(urls.count)
for try await pair in group {
indexedResults.append(pair)
}
return indexedResults.sorted { $0.0 < $1.0 }.map(\.1)
}
}
Result-builder API
使用 @resultBuilder 和同质组件枚举(或 buildExpression 重载),以便调用方能按任意顺序组合请求,并使用可选分支:
// CALL SITE:
let request = HTTPRequest(url: endpoint) {
Method(.post)
Header("Accept", "application/json")
if includeAuth {
BearerToken(token)
}
Timeout(.seconds(30))
}
// IMPLEMENTATION:
public enum RequestComponent: Sendable {
case method(HTTPMethod)
case header(String, String)
case bearerToken(String)
case timeout(Duration)
}
public func Method(_ method: HTTPMethod) -> RequestComponent { .method(method) }
public func Header(_ name: String, _ value: String) -> RequestComponent { .header(name, value) }
public func BearerToken(_ token: String) -> RequestComponent { .bearerToken(token) }
public func Timeout(_ duration: Duration) -> RequestComponent { .timeout(duration) }
@resultBuilder
public struct RequestBuilder {
public static func buildExpression(_ component: RequestComponent) -> [RequestComponent] {
[component]
}
public static func buildBlock(_ components: [RequestComponent]...) -> [RequestComponent] {
components.flatMap { $0 }
}
public static func buildOptional(_ component: [RequestComponent]?) -> [RequestComponent] {
component ?? []
}
public static func buildEither(first component: [RequestComponent]) -> [RequestComponent] {
component
}
public static func buildEither(second component: [RequestComponent]) -> [RequestComponent] {
component
}
}
public struct HTTPRequest: Sendable {
public let url: URL
public let components: [RequestComponent]
public init(url: URL, @RequestBuilder content: () -> [RequestComponent]) {
self.url = url
self.components = content()
}
}
库演进
1. 公共与内部边界及弃用
除非直接修改属于契约的一部分,否则将存储属性设为 private 或 internal;弃用 API 时,提供可操作的 renamed: 或 message: 指引:
public struct Endpoint: Sendable, Equatable {
public let path: String
internal let cacheKey: String
public init(path: String) {
self.path = path
self.cacheKey = path.lowercased()
}
@available(*, deprecated, renamed: "init(path:)", message: "Pass a path string directly.")
public init(rawPath: String) {
self.init(path: rawPath)
}
}
2. 受约束的集合扩展
为标准库协议提供便捷扩展时,应返回新的排序或筛选后集合,而不是就地修改:
public protocol Sortable {
associatedtype SortKey: Comparable
var sortKey: SortKey { get }
}
extension Sequence where Element: Sortable {
public func sortedByKey() -> [Element] {
sorted { $0.sortKey < $1.sortKey }
}
}
常见任务
设计公共 SDK API
- 调用点优先:起草第 1 层(零配置)、第 2 层(流畅配置)和第 3 层(协议 DI)的用法片段。
- 从对流层开始:在
public init中只要求必要参数(例如baseURL),并为timeout和retryPolicy等策略设置默认值。 - 确保遵循
Sendable规范:将公共值类型、协议(protocol Transport: Sendable)和闭包(@Sendable)标记为符合 Swift 6 的要求。 - 为演进做好规划:若具体结构体已足够,就避免公开没有默认实现的公共协议;对不稳定 API 使用
@_spi(Experimental)。
将库迁移到 Swift 6
- 启用完整的并发检查(
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]或 Swift 6 语言模式)。 - 检查 Actor 在
await挂起点前后的状态假设。 - 将
NSLock+@unchecked Sendable类替换为Mutex或actor。 - 在公开的依赖注入协议中添加
: Sendable,以便模拟对象和实现可以跨 Actor 边界传递。
拒绝标准
除 stratos-core/SKILL.md 中的核心拒绝标准外,还应拒绝:
- Actor 重入错误:在
await之前检查 Actor 状态,却在之后直接修改状态,既不重新验证,也不对进行中的任务去重。 - 缺乏依据的
@unchecked Sendable:使用@unchecked Sendable来掩盖编译器错误,却无法证明锁的不变量(或写出无效的@unchecked Sendable属性语法,而不是: @unchecked Sendable)。 - 不符合
Sendable的公开协议:定义异步服务/传输协议时不添加: Sendable,导致这些协议在 Swift 6 中无法用于 Actor 和@MainActor视图。 - 僵化的结果构建器:设计只能按固定的必需顺序接受固定元组参数的
@resultBuilder代码块。 - 无类型字典和
Any:在公开 API 中暴露[String: Any],而不是使用Codable结构体、枚举或泛型。
另请参阅
- stratos-core — 渐进式披露的核心方法论
- references/LAYERS.md — Swift 库各层的详细模式和迁移检查清单
- stratos-swiftui — SwiftUI 组件实现
延伸阅读
- Swift 中的渐进式披露(Swift Craft 2025)— Doug Gregor 讲解 Swift 如何将渐进式披露应用于语言设计,包括 Typed Throws、Non-Copyable Types 和并发演进。