stratos-swift について
stratos-swift は、Swift SDK とライブラリ設計向けのエージェントスキルです。Swift 6 の並行処理、型安全性、Result Builder DSL、ライブラリの進化を扱い、呼び出し例を先に示して API 設計や移行を案内します。
主な機能
- 01Swift SDK と公開 API の設計
- 02Swift 6 の並行処理と Sendable の指針
- 03型安全な Result Builder DSL の構築
- 04ライブラリの進化と非推奨化の計画
- 05Swift 6 への移行ガイド
活用シーン
- 01使いやすく進化可能な Swift SDK の設計
- 02Swift 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 のコードブロックには、必ず想定する呼び出し側のコードを記載します。呼び出し側のコードより前にエラー列挙型、プロトコル、構造体、アクターを定義してはならず、使用例を回答の最後まで後回しにしてもいけません。
// 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 の 型付き Throw(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 内の状態は、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)
- 自然に
Sendableに準拠する 値型(struct、enum) とactor型を優先します。 asyncによる一時停止を伴わずに同期的なスレッドセーフ状態が必要な場合は、自然にSendableに準拠し、@uncheckedを必要としない Swift 6 のMutex(Synchronizationから提供)を優先します。
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. 構造化並行処理とキャンセルに安全なストリーム
非構造化の Task {} ループではなく withThrowingTaskGroup を優先します。また、コールバック 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
均質なコンポーネント列挙型(または buildExpression のオーバーロード)を使って @resultBuilder を実装し、呼び出し側が任意の順序でリクエストを組み合わせたり、条件分岐を省略可能にしたりできるようにします。
// 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 にします。また、非推奨化には、実行可能な移行方法を示す 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の原則を徹底する:Swift 6 との互換性を確保するため、パブリックな値型、プロトコル(protocol Transport: Sendable)、クロージャ(@Sendable)に適切な指定を付けます。- 進化を見越して設計する:デフォルト実装がないパブリックプロトコルを公開するのは避け、具体的な構造体で十分な場合は構造体を使用します。不安定な API には
@_spi(Experimental)を使用します。
ライブラリを Swift 6 に移行する
- 並行処理の完全なチェックを有効にします(
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]または Swift 6 言語モード)。 awaitの中断ポイントをまたぐ状態の前提について、アクターを監査します。NSLockと@unchecked Sendableを使用するクラスをMutexまたはactorに置き換えます。- モックと実装がアクター境界を越えられるよう、公開する依存性注入プロトコルに
: Sendableを追加します。
拒否基準
stratos-core/SKILL.md にある基本的な拒否基準に加えて、以下も拒否します。
- アクターの再入可能性に関するバグ:
awaitの前にアクターの状態を確認し、その後に再検証や進行中のタスクの重複排除をせずに状態を変更すること。 - 根拠のない
@unchecked Sendable:実証されたロック不変条件なしにコンパイラーエラーを抑制する目的で@unchecked Sendableを使用すること(または: @unchecked Sendableの代わりに、無効な@unchecked Sendable属性構文を記述すること)。 Sendableでない公開プロトコル:: Sendableを付けずに非同期のサービス/トランスポートプロトコルを定義すること。これにより、Swift 6 でアクターや@MainActorビューから利用できなくなります。- 柔軟性のない結果ビルダー:必須の順序で固定された引数のタプルのみを受け付ける
@resultBuilderブロックを設計すること。 - 型指定のない辞書と
Any:Codable構造体、列挙型、ジェネリクスの代わりに、公開 API で[String: Any]を公開すること。
関連項目
- stratos-core — プログレッシブ開示の基本手法
- references/LAYERS.md — Swift ライブラリのレイヤーパターンと移行チェックリストの詳細
- stratos-swiftui — SwiftUI コンポーネントの実装
参考資料
- Swift におけるプログレッシブ開示について(Swift Craft 2025)— Doug Gregor が、Typed Throws、Non-Copyable Types、並行処理の進化など、Swift が言語設計にプログレッシブ開示をどのように適用しているかを解説します。