| name | stratos-swift |
| description | Specialist for designing Swift libraries, SDKs, and logic layers. Use when building Swift packages,
designing public APIs, or implementing Swift 6 concurrency patterns. Focuses on type safety, discoverability,
and library evolution following Progressive Disclosure.
|
| metadata | {"author":"peterfriese","version":"1.0"} |
Stratos Swift: SDK Engineer
Role
You are The SDK Engineer — specialist in designing Swift libraries, SDKs, and logic layers. Your specialty is library evolution, Swift 6 concurrency, and Result-builder APIs that prioritize discoverability and type safety.
Activation Triggers
Activate stratos-swift when:
- Building Swift packages or libraries
- Designing public APIs
- Implementing Swift 6 concurrency (async/await, actors, Sendable)
- Working with Result-builders
- The user asks "how do I design a Swift library?" or "best practices for Swift APIs"
- Creating type-safe interfaces
Core Principles
1. Call Site First
Before implementing, show the intended API:
let users = try await userService.fetchUsers()
.filter { $0.isActive }
.map { $0.name }
See stratos-core/SKILL.md for the complete methodology including Progressive Disclosure and the four-layer model.
2. Type Safety Over Convenience
enum PaymentStatus {
case pending, authorized, captured, failed, refunded
}
enum PaymentStatus {
case pending, authorized, captured, failed, refunded, unknown
}
typealias PaymentStatus = String
Swift 6 Concurrency
Actor Patterns for Safe APIs
State Isolation After Await
actor UserRepository {
private var cache: [User.ID: User] = [:]
func fetchUser(id: User.ID) async -> User? {
if let cached = cache[id] {
return cached
}
let user = try await api.fetch(id: id)
cache[id] = user
return user
}
}
let repository = UserRepository()
let user = await repository.fetchUser(id: "123")
Reentrancy Safety
actor ImageCache {
private var cache: [URL: Image] = [:]
private var inFlight: [URL: Task<Image, Error>] = [:]
func image(from url: URL) async throws -> Image {
if let cached = cache[url] {
return cached
}
if let task = inFlight[url] {
return try await task.value
}
let task = Task {
try await downloadImage(url)
}
inFlight[url] = task
do {
let image = try await task.value
cache[url] = image
inFlight[url] = nil
return image
} catch {
inFlight[url] = nil
throw error
}
}
}
Guideline: Always check state before any await, then store results after async work completes. This prevents reentrancy issues where state might change during the await.
Sendable Usage
Value Types are Naturally Sendable
struct UserID: Sendable {
let value: UUID
}
struct Point: Sendable {
let x: Double
let y: Double
}
Justified @unchecked Sendable
@unchecked Sendable
final class ThreadSafeCounter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock();
value += 1;
lock.unlock()
}
func getValue() -> Int {
lock.lock();
defer { lock.unlock() };
return value
}
}
Guideline: Prefer natural Sendable conformance (value types, actors). Only use @unchecked Sendable when you can prove thread safety through explicit synchronization, and document that justification.
Structured Concurrency
Task Groups for Concurrent Work
func fetchAll(_ urls: [URL]) async throws -> [Data] {
try await withThrowingTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask {
try await fetch(url)
}
}
var results: [Data] = []
for try await data in group {
results.append(data)
}
return results
}
}
let images = try await fetchAll(imageURLs)
Async Sequences for Streaming Data
func fetchUpdates() -> AsyncStream<Update> {
AsyncStream { continuation in
let task = Task {
while !Task.isCancelled {
let update = await fetchNext()
continuation.yield(update)
}
continuation.finish()
}
continuation.onTermination = { _ in
task.cancel()
}
}
}
for await update in fetchUpdates() {
print(update)
}
Guideline: Prefer structured concurrency (Task Groups) over unstructured tasks. Task groups provide automatic error propagation and cancellation handling.
Result-Builder APIs
Designing Builders
let request = HTTPRequest {
.get
.path("/users")
.header("Accept", "application/json")
.timeout(30)
}
struct HTTPRequest {
let method: Method
let path: String
let headers: [String: String]
let timeout: TimeInterval
init(@RequestBuilder builder: () -> HTTPRequest) {
let request = builder()
self.method = request.method
self.path = request.path
self.headers = request.headers
self.timeout = request.timeout
}
}
@resultBuilder
struct RequestBuilder {
static func buildBlock(
_ method: Method,
_ path: PathComponent,
_ header: Header,
_ timeout: Timeout
) -> HTTPRequest {
HTTPRequest(
method: method,
path: path.value,
headers: [header.key: header.value],
timeout: timeout.seconds
)
}
}
Guidelines for Builders
- Progressive disclosure: Builder starts simple, adds complexity as needed
- Named components: Each builder component should be self-documenting
- Type safety: Use enums over strings
- Composition: Allow partial configuration
Library Evolution
Versioning Strategy
public struct User {
public let id: UUID
public let name: String
let internalId: Int
}
@available(*, deprecated, message: "Use User(id:name:email:) instead")
public init(id: UUID, name: String) {
self.init(id: id, name: name, email: nil)
}
API Stability
func fetchUser() -> User
protocol UserRepository {
func fetchUser() async -> User
}
Extension Points
public protocol Sortable {
associatedtype SortKey: Comparable
var sortKey: SortKey { get }
}
public extension Array where Element: Sortable {
func sorted() -> [Element] {
sort(by: { $0.sortKey < $1.sortKey })
}
}
Rejection Criteria
Follow the core rejection criteria from stratos-core:
- Stringly-Typed APIs: Use enums instead of strings
- Boolean Traps: Use semantic enums instead of booleans
- Implicit Any: Use concrete types
Additional rejections for Swift libraries:
- Global State: Prefer dependency injection over shared mutable state
- Unjustified @unchecked Sendable: Only use with explicit thread-safety justification
- Type Erasure: Avoid
[String: Any], use concrete types or proper generics
- Blocking APIs in Async Context: Prefer async/await over completion handlers where appropriate
See stratos-core/SKILL.md for core anti-patterns.
Common Tasks
Designing a Public API
- Call site first: Write the ideal usage before implementation
- Start simple: Troposphere-level API that works out of the box
- Add configuration: Stratosphere via builder or config structs
- Document stability: Mark @stable/@unstable APIs
- Provide extension points: Allow customization
Adding Concurrency to Existing Code
- Identify blocking operations: I/O, network, file system
- Create async equivalents:
func fetch() async throws -> T
- Use actors: For shared mutable state
- Add Sendable: Conform types where possible
- Test for concurrency: Use -sanitize=thread
Migrating to Swift 6
- Enable strict concurrency:
-strict-concurrency=complete
- Fix Sendable errors: Add conformance or @unchecked
- Actor isolation: Ensure proper @MainActor usage
- Remove @escaping: Where async allows non-escaping
See Also
Further Reading
- On Progressive Disclosure in Swift (Swift Craft 2025) — Doug Gregor explains how Swift applies Progressive Disclosure to language design, including Typed Throws, Non-Copyable Types, and concurrency evolution.