agentleFS
piemontepiemonte/skills/swift-concurrency/SKILL.md

swift-concurrency

Skill1 starsChanged 7 months agoMITView on GitHub

Use when writing concurrent Swift code — actors, Sendable, AsyncSequence, task cancellation, synchronization, state machines, reactive patterns, generics, testing, and diagnostics. Targets Swift 6 strict concurrency.

25 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use swiftdata, swiftui-expert-skill and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • Prisma-Labs-DevSkill

    swiftdata

    API reference: SwiftData. Query for @Model, ModelContext, @Query, schema migrations.

    Also in 3 projects that use this

  • xfstudioSkill

    swiftui-expert-skill

    Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, modern APIs, Swift concurrency, and iOS 26+ Liquid Glass adoption. Use when building new SwiftUI features, refactoring existing views, reviewing code quality, or adopting modern SwiftUI patterns.

    Also in 8 projects that use this

  • pluginagentmarketplaceSkill

    swift-testing

    Test Swift applications - XCTest, Swift Testing, UI tests, mocking, TDD, CI/CD

    Also in 4 projects that use this

  • eugenepyvovarovSkill

    swift-concurrency-expert

    Swift Concurrency review and remediation for Swift 6.2+. Use when asked to review Swift Concurrency usage, improve concurrency compliance, or fix Swift concurrency compiler errors in a feature or file.

    Also in 4 projects that use this

  • AvdLeeSkill

    swift-testing-expert

    Expert guidance for Swift Testing: test structure, #expect/#require macros, traits and tags, parameterized tests, test plans, parallel execution, async waiting patterns, and XCTest migration. Use when writing new Swift tests, modernizing XCTest suites, debugging flaky tests, or improving test quality and maintainability in Apple-platform or Swift server projects.

    Also in 3 projects that use this

  • dadederkSkill

    ios-accessibility

    Expert guidance on iOS accessibility best practices, patterns, and implementation. Use when developers mention: (1) iOS accessibility, VoiceOver, Dynamic Type, or assistive technologies, (2) accessibility labels, traits, hints, or values, (3) automated accessibility testing, auditing, or manual testing, (4) Switch Control, Voice Control, or Full Keyboard Access, (5) inclusive design or accessibility culture, (6) making apps work for users with disabilities.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

7 rules. 0 are common, 2 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 24 edited versions we read keep these.

  • 2

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All shared data types conform to Sendable

    line 28
  2. Distinctive:

    No data races detected by compiler

    line 31
  3. Very specific:

    Check Task.isCancelled before updating state

    line 294
  4. Very specific:

    Use [weak self] in Task closures

    line 295
  5. Very specific:

    Use descriptive case names (.invalidURL not .error1)

    line 427
  6. Very specific:

    Never use generic NSError in new code

    line 431
  7. Distinctive:

    Start with most restrictive access level.

    line 788

“Common” is measured: kept by at least 6 in 10 of the 24 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Swift Concurrency & Patterns Reference

Swift 6 Strict Concurrency

Configuration

// Package.swift
swiftSettings: [
    .enableUpcomingFeature("StrictConcurrency")
]
// Xcconfig
SWIFT_VERSION = 6.0
SWIFT_STRICT_CONCURRENCY = complete
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES

Thread-Safety Checklist

  • All shared data types conform to Sendable
  • Mutable state protected by actors or synchronization primitives
  • Closures marked with @Sendable when crossing concurrency boundaries
  • No data races detected by compiler

Actor Patterns

Standard Actor

actor DataProcessor {
    private var state: ProcessingState = .idle
    private var subscriptions = [UUID: Subscription]()

    nonisolated let identifier: UUID  // Safe cross-actor access

    func process(_ data: Data) async throws -> Result {
        // Automatically isolated to this actor
    }
}

Actor Service (Preferred for Services)

actor APIService {
    private let session: URLSession

    init() {
        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 60.0
        self.session = URLSession(configuration: config)
    }

    func fetchData() async throws -> [Item] {
        let (data, _) = try await session.data(for: request)
        return try JSONDecoder().decode([Item].self, from: data)
    }
}

Isolation Context Propagation

protocol ServiceDelegate: Sendable {
    func serviceDidUpdate(
        isolation: isolated (any Actor)?,
        value: SomeValue
    ) async
}

Safe Delegation in Actors

actor Service {
    private nonisolated(unsafe) weak var _delegate: Delegate?

    private var delegate: Delegate {
        get throws {
            guard let _delegate else {
                throw ServiceError.delegateUnavailable
            }
            return _delegate
        }
    }

    func notifyDelegate() async throws {
        try await delegate.serviceDidUpdate(self)
    }
}

Sendable Conformance

Structs (Preferred)

struct DataModel: Codable, Identifiable, Hashable, Sendable {
    let id: UUID
    let name: String
    let timestamp: Date
    // All properties must be Sendable types
}

Sendable-Compatible Types

  • Primitive types: String, Int, Bool, Double, Float
  • Standard library: Date, URL, UUID, Data
  • Collections of Sendable: [String], [Int: String]
  • Enums with Sendable associated values
  • Other Sendable structs

Classes with Manual Thread Safety

final class ThreadSafeCache: @unchecked Sendable {
    private let lock = NSLock()
    private var cache: [String: Data] = [:]

    func get(_ key: String) -> Data? {
        lock.lock()
        defer { lock.unlock() }
        return cache[key]
    }

    func set(_ key: String, value: Data) {
        lock.lock()
        defer { lock.unlock() }
        cache[key] = value
    }
}

Non-Sendable Third-Party Libraries

@preconcurrency import ThirdPartyLib
@preconcurrency import AVFoundation
@preconcurrency import FirebaseFirestore

AsyncSequence & AsyncStream

AsyncThrowingStream for Progress

func export() -> AsyncThrowingStream<ExportEvent, Error> {
    AsyncThrowingStream { continuation in
        Task {
            do {
                for progress in stride(from: 0.0, through: 1.0, by: 0.1) {
                    try Task.checkCancellation()
                    continuation.yield(.progress(Float(progress)))
                }
                continuation.yield(.completed(outputURL))
                continuation.finish()
            } catch {
                continuation.finish(throwing: error)
            }
        }
    }
}

// Consumption
for try await update in service.export() {
    switch update {
    case .progress(let value): print("Progress: \(value)")
    case .completed(let url): print("Done: \(url)")
    }
}

AsyncStream for Real-Time Updates (Firebase, Location, etc.)

func observeChanges(id: String) -> AsyncStream<[Item]> {
    AsyncStream { continuation in
        let listener = database.collection("items")
            .document(id)
            .addSnapshotListener { snapshot, error in
                guard let snapshot = snapshot else { return }
                let items = snapshot.documents.compactMap { doc in
                    try? Item(from: doc.data())
                }
                continuation.yield(items)
            }

        continuation.onTermination = { _ in
            listener.remove()  // Clean up listener
        }
    }
}

AsyncNotifier (Multi-Subscriber Broadcast)

final class AsyncNotifier<Value: Sendable>: Sendable {
    private struct State {
        var continuations: [UUID: AsyncStream<Value>.Continuation] = [:]
        var currentValue: Value?
    }

    private let state = OSAllocatedUnfairLock(initialState: State())

    func yield(_ value: Value) {
        let continuations = state.withLock { state in
            state.currentValue = value
            return Array(state.continuations.values)
        }
        for continuation in continuations {
            continuation.yield(value)
        }
    }

    var sequence: AsyncStream<Value> {
        let (stream, continuation) = AsyncStream<Value>.makeStream()
        let id = UUID()
        state.withLock { state in
            state.continuations[id] = continuation
            if let current = state.currentValue {
                continuation.yield(current)
            }
        }
        continuation.onTermination = { [weak self] _ in
            self?.state.withLock { state in
                state.continuations[id] = nil
            }
        }
        return stream
    }
}

AsyncBroadcastChannel

final class AsyncBroadcastChannel<Element: Sendable>: Sendable, AsyncSequence {
    func makeAsyncIterator() -> AsyncIterator { /* ... */ }
    func broadcast(_ element: Element) async { /* ... */ }
}

Task Cancellation & Resource Management

@MainActor
final class ViewModel: ObservableObject {
    @Published var isLoading = false
    private var loadingTask: Task<Void, Never>?

    deinit {
        loadingTask?.cancel()  // Prevent memory leaks
    }

    func startLoading() {
        loadingTask?.cancel()  // Cancel previous
        loadingTask = Task { @MainActor [weak self] in
            guard let self, !Task.isCancelled else { return }
            do {
                let result = try await service.fetchData()
                guard !Task.isCancelled else { return }
                self.data = result
            } catch {
                guard !Task.isCancelled else { return }
                self.errorMessage = error.localizedDescription
            }
        }
    }
}

Cancellation Checklist

  • Store long-running tasks as properties
  • Cancel tasks in deinit
  • Check Task.isCancelled before updating state
  • Use [weak self] in Task closures
  • Cancel previous tasks before starting new ones
  • Invalidate timers in deinit

Synchronization Primitives

Mutex (Preferred for Simple State)

import Synchronization

final class StateContainer: @unchecked Sendable {
    private let state = Mutex<State>()

    struct State {
        var pending: [Job] = []
        var active: Job?
    }

    func enqueue(_ job: Job) {
        state.withLock { $0.pending.append(job) }
    }

    func dequeue() -> Job? {
        state.withLock { $0.pending.isEmpty ? nil : $0.pending.removeFirst() }
    }
}

Selection Criteria

NeedUse
Complex stateful objectsactor
Simple value protectionMutex
Low-level synchronizationOSAllocatedUnfairLock
Avoid manual locking when possibleactor

State Machines

actor StateMachine<State: Hashable, Event: Hashable> {
    private(set) var currentState: State
    private var transitions: [State: [Event: State]]

    init(initial: State) {
        self.currentState = initial
        self.transitions = [:]
    }

    func addTransition(from: State, on: Event, to: State) {
        transitions[from, default: [:]][on] = to
    }

    func execute(_ event: Event) -> Bool {
        guard let next = transitions[currentState]?[event] else { return false }
        currentState = next
        return true
    }
}

Reactive Patterns

AsyncNotifying Protocol

protocol AsyncNotifying: Sendable {
    associatedtype Value: Sendable
    var value: Value { get async }
    func subscribe(
        receiveValue: @escaping @isolated(any) @Sendable (_ newValue: Value) async -> ()
    ) -> Subscription
}

Reactive Property Wrapper

@propertyWrapper
struct UserDefault<Value: Codable> {
    let key: String
    let defaultValue: Value
    var wrappedValue: Value { get set }
    var projectedValue: AsyncStream<Value> { get }  // Observe changes
}

Error Handling

Domain-Specific LocalizedError

enum NetworkError: LocalizedError, Sendable {
    case connectionFailed(reason: String)
    case timeout(duration: TimeInterval)
    case invalidResponse(statusCode: Int)

    var errorDescription: String? {
        switch self {
        case .connectionFailed(let reason):
            return "Connection failed: \(reason)"
        case .timeout(let duration):
            return "Request timed out after \(duration)s"
        case .invalidResponse(let code):
            return "Invalid response: HTTP \(code)"
        }
    }

    var recoverySuggestion: String? {
        switch self {
        case .timeout:
            return "Check your network connection and try again"
        default:
            return nil
        }
    }
}

Error Handling Best Practices

  • Define errors in the same file as the service
  • Handle at ViewModel layer, not in Views
  • Use descriptive case names (.invalidURL not .error1)
  • Include context via associated values
  • User-friendly messages in errorDescription
  • Make all error enums Sendable
  • Never use generic NSError in new code

Data Persistence

Generic Record Store

protocol RecordStore: Sendable {
    associatedtype Record: Sendable
    func record(for key: String) async -> Record?
    func allRecords() async -> [String: Record]
    func store(_ record: Record, for key: String) async
    func delete(key: String) async
}

Type-Safe File I/O

final class DiskIO<T: Codable>: Sendable {
    let fileURL: URL
    let protectionType: FileProtectionType

    func read() async throws -> T
    func write(_ value: T) async throws
    func delete() async throws
    var changes: AsyncStream<T> { get }
}

Networking & Flow Control

Rate Limiting (Token Bucket)

actor RateLimiter {
    let capacity: Int
    let refillRate: TimeInterval
    private var tokens: Double
    private var lastRefill: Date

    func allowRequest() async -> Bool {
        let now = Date()
        let elapsed = now.timeIntervalSince(lastRefill)
        tokens = min(Double(capacity), tokens + elapsed / refillRate)
        lastRefill = now
        guard tokens >= 1 else { return false }
        tokens -= 1
        return true
    }
}

Back-Pressure

final class BackPressureChannel<Element: Sendable>: AsyncSequence {
    func send(_ element: Element) async {
        // Suspends until consumers process the value
    }
}

Generics & Type Safety

Generic Constraints

protocol DataTransformer {
    associatedtype Input
    associatedtype Output
    func transform(_ input: Input) async throws -> Output
}

extension DataTransformer where Input: Codable, Output: Codable {
    func transformAndSerialize(_ input: Input) async throws -> Data {
        let output = try await transform(input)
        return try JSONEncoder().encode(output)
    }
}

Type Erasure

struct AnyDataProvider<Element: Sendable>: DataProvider {
    private let _start: @Sendable () async throws -> Void
    private let _elements: @Sendable () async -> AsyncStream<Element>

    init<P: DataProvider>(_ provider: P) where P.Element == Element {
        _start = provider.start
        _elements = { await provider.elements }
    }
}

Type Aliases for Domain Clarity

typealias UserIdentifier = String
typealias SessionToken = String
typealias Timestamp = UInt64

IPC (Inter-Process Communication)

protocol Message: Sendable, Codable {
    associatedtype Reply: Sendable, Codable
}

protocol IPCInterface {
    associatedtype ClientMessages: MessagesProtocol
    associatedtype HostMessages: MessagesProtocol
}

Testing Patterns

Async Testing

final class ServiceTests: XCTestCase {
    var sut: SystemUnderTest!
    var mockDependency: MockDependency!

    override func setUp() async throws {
        mockDependency = MockDependency()
        sut = SystemUnderTest(dependency: mockDependency)
    }

    override func tearDown() async throws {
        await sut.cleanup()
        sut = nil
    }

    func testFeatureBehavior() async throws {
        // Arrange
        mockDependency.configureResponse(.success)
        // Act
        let result = try await sut.performAction()
        // Assert
        XCTAssertEqual(result, expectedValue)
        XCTAssertTrue(mockDependency.methodWasCalled)
    }
}

Cancellation Testing

func testCancellation() async throws {
    let task = Task { try await service.longRunningWork() }
    try await Task.sleep(nanoseconds: 100_000_000)
    task.cancel()
    do {
        _ = try await task.value
        XCTFail("Should have been cancelled")
    } catch is CancellationError {
        // Expected
    }
}

Async Sequence Test Iterator

actor AsyncSequenceTestIterator<S: AsyncSequence> {
    func waitForNext(timeout: Duration = .seconds(1)) async throws -> S.Element
    func assertRemainsEmpty(for duration: Duration) async throws
    func finish() async
}

Mock Guidelines

  • Complete protocol implementations
  • Configurable behavior (success/failure modes)
  • Capture method calls for verification
  • Support async/await patterns

Test Organization

Tests/
├── Unit/              # Isolated component tests
├── Integration/       # Multi-component tests
├── Mocks/            # Mock implementations
└── Utilities/        # Test helpers and assertions

Logging & Diagnostics

Unified Logging

import OSLog

extension Logger {
    static let networking = Logger(subsystem: "com.app", category: "networking")
    static let storage = Logger(subsystem: "com.app", category: "storage")
    static let services = Logger(subsystem: "com.app", category: "services")
}

// Usage
Logger.networking.info("Request started: \(url)")
Logger.networking.error("Request failed: \(error)")

Performance Monitoring

import OSLog

let signposter = OSSignposter(logger: Logger.performance)

func performCriticalOperation() async {
    let state = signposter.beginInterval("CriticalOperation")
    defer { signposter.endInterval("CriticalOperation", state) }
    // Operation
}

Service-Oriented Architecture

Protocol-Based Services

protocol ServiceProtocol: Sendable {
    func stop() async
}

actor ServiceManager {
    static let shared = ServiceManager()
    private var services: [String: any ServiceProtocol] = [:]

    func register<T: ServiceProtocol>(_ service: T, for key: String) {
        services[key] = service
    }

    func service<T: ServiceProtocol>(for key: String) -> T? {
        services[key] as? T
    }
}

Provider Pattern

protocol DataProvider: Sendable {
    associatedtype Element: Sendable
    var state: ProviderState { get async }
    var elements: AsyncStream<Element> { get async }
    func start() async throws
    func pause() async
    func resume() async
    func stop() async
}

enum ProviderState: Sendable {
    case initialized, running, paused, stopped
}

CLI Tools

import ArgumentParser

@main
struct CLI: AsyncParsableCommand {
    static let configuration = CommandConfiguration(
        commandName: "tool",
        abstract: "A utility for managing the framework",
        subcommands: [Start.self, Stop.self, Status.self]
    )
}

struct Start: AsyncParsableCommand {
    @Option(help: "Configuration file path")
    var config: String?

    @Flag(help: "Enable verbose output")
    var verbose = false

    mutating func run() async throws { }
}

Documentation Standards

DocC Format

/// Performs an asynchronous operation with retry logic.
///
/// - Parameters:
///   - operation: The async operation to perform
///   - maxRetries: Maximum number of retry attempts
/// - Returns: The result of the operation
/// - Throws: The last error encountered if all retries fail
///
/// ## Example
/// ```swift
/// let result = try await performWithRetry { try await fetchData() }
/// ```
func performWithRetry<T>(
    operation: @Sendable () async throws -> T,
    maxRetries: Int = 3
) async throws -> T

Code Organization

// MARK: - Lifecycle
init() { }

// MARK: - Public API
public func performAction() async throws { }

// MARK: - Private Implementation
private func helperMethod() { }

// MARK: - Protocol Conformance
extension MyClass: SomeProtocol { }

Access Control

LevelUse
publicFramework API surface
packageShared between framework targets
internalDefault, implementation details
privateNarrowest scope possible

Start with most restrictive access level. Expand only when necessary.


Build Configuration

Xcconfig Structure

Configs/
├── Base.xcconfig           # Shared settings
├── Framework.xcconfig      # Framework-specific
├── Tests.xcconfig         # Test target settings
└── Tools.xcconfig         # CLI tools

Base Settings

SWIFT_VERSION = 6.0
SWIFT_STRICT_CONCURRENCY = complete
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES
GCC_TREAT_WARNINGS_AS_ERRORS = YES
CLANG_ANALYZER_NONNULL = YES
CLANG_STATIC_ANALYZER_MODE = deep
ENABLE_TESTABILITY = YES
CLANG_ENABLE_MODULES = YES

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.