All skills
avdlee avatar

/swift-concurrency

@35095d4

Diagnose Swift Concurrency issues, refactor callback-based code to async/await, and guide Swift 6 migration when working with tasks, actors, @MainActor, Sendable, data races, thread safety, or concurrency-related compiler and linter warnings.

Use this Skill: https://skilld.dev/gh/avdlee/swift-concurrency-agent-skill/swift-concurrency

This session only. Nothing lands on disk.

referencesasync-await-basics.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Async/Await Basics

Use this when:

  • You are starting fresh with async/await and need foundational patterns.
  • You are converting callback-based code to async/await.
  • You need to understand execution order and the sync-to-async bridge.

Skip this file if:

  • You need parallel execution with task groups or async let. Use tasks.md.
  • You need stream-based async iteration. Use async-sequences.md.

Jump to:

  • Function Declaration
  • Execution Order
  • Parallel Execution with async let
  • URLSession with Async/Await
  • Migration Strategy

Function Declaration

Mark functions with async to indicate asynchronous work:

func fetchData() async -> Data {
    // async work
}

func fetchData() async throws -> Data {
    // async work that can fail
}

Key benefit over closures: The compiler enforces return values. No forgotten completion handlers.

Course Deep Dive: This topic is covered in detail in Lesson 2.1: Introduction to async/await syntax

Calling Async Functions

From synchronous context

Use Task to bridge from sync to async:

Task {
    let data = try await fetchData()
}

From async context

Use await directly:

func processData() async throws {
    let data = try await fetchData()
    // process data
}

Execution Order

Structured concurrency executes top-to-bottom in the order you expect:

let first = try await fetchData(1)   // Waits for completion
let second = try await fetchData(2)  // Starts after first completes
let third = try await fetchData(3)   // Starts after second completes

Code after await only executes once the awaited function returns.

Course Deep Dive: This topic is covered in detail in Lesson 2.2: Understanding the order of execution

Parallel Execution with async let

Use async let to run multiple operations concurrently:

async let data1 = fetchData(1)
async let data2 = fetchData(2)
async let data3 = fetchData(3)

let results = try await [data1, data2, data3]

How async let works

  • Starts immediately: The function executes right away, even before await
  • Structured concurrency: Automatically canceled when leaving scope
  • Error handling: If one fails, others are implicitly canceled when awaiting grouped results
  • No redundant keywords: Don't use try await in the async let line itself
// Redundant - avoid this
async let data = try await fetchData()

// Correct - errors handled at await point
async let data = fetchData()
let result = try await data

When to use async let

Use when:

  • Tasks don't depend on each other
  • Number of tasks known at compile-time
  • Want automatic cancellation on scope exit

Avoid when:

  • Tasks must run sequentially
  • Need dynamic task spawning (use TaskGroup)
  • Need manual cancellation control

Limitations

  • Cannot use at top-level declarations (only within function bodies)
  • Tasks not explicitly awaited may be canceled implicitly

Course Deep Dive: This topic is covered in detail in Lesson 2.3: Calling async functions in parallel using async let

URLSession with Async/Await

URLSession provides async alternatives to closure-based APIs:

// Closure-based (old)
URLSession.shared.dataTask(with: request) { data, response, error in
    guard let data = data, error == nil else { return }
    // handle response
}.resume()

// Async/await (modern)
let (data, response) = try await URLSession.shared.data(for: request)

Benefits over closures

  • No optional data or response to unwrap
  • Automatic error throwing
  • Compiler enforces return values
  • Simpler error handling with do-catch

Complete network request pattern

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    
    let (data, response) = try await URLSession.shared.data(for: request)
    
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        throw NetworkError.invalidResponse
    }
    
    return try JSONDecoder().decode(User.self, from: data)
}

POST requests with JSON

func createUser(_ user: User) async throws -> User {
    let url = URL(string: "https://api.example.com/users")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try JSONEncoder().encode(user)
    
    let (data, response) = try await URLSession.shared.data(for: request)
    
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        throw NetworkError.invalidResponse
    }
    
    return try JSONDecoder().decode(User.self, from: data)
}

Course Deep Dive: This topic is covered in detail in Lesson 2.4: Performing network requests using URLSession and async/await

Typed Errors (Swift 6)

Specify exact error types for better API contracts:

enum NetworkError: Error {
    case invalidResponse
    case decodingFailed(DecodingError)
    case requestFailed(URLError)
}

func fetchData() async throws(NetworkError) -> Data {
    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    } catch let error as URLError {
        throw .requestFailed(error)
    } catch {
        throw .invalidResponse
    }
}

Callers know exactly which errors to handle.

Migration Strategy

When converting closure-based code:

  1. Add new async method alongside old one - keeps code compiling
  2. Update method signature - add async, remove completion parameter
  3. Replace closure calls with await - use URLSession async APIs
  4. Remove optional unwrapping - async APIs return non-optional values
  5. Simplify error handling - use do-catch instead of nested closures
  6. Return directly - compiler enforces return values

Common Patterns

Sequential execution (when order matters)

let user = try await fetchUser(id: 1)
let posts = try await fetchPosts(userId: user.id)
let comments = try await fetchComments(postIds: posts.map(\.id))

Parallel execution (when independent)

async let user = fetchUser(id: 1)
async let settings = fetchSettings()
async let notifications = fetchNotifications()

let (userData, settingsData, notificationsData) = try await (user, settings, notifications)

Mixed execution

// Fetch user first (required for next step)
let user = try await fetchUser(id: 1)

// Then fetch related data in parallel
async let posts = fetchPosts(userId: user.id)
async let followers = fetchFollowers(userId: user.id)
async let following = fetchFollowing(userId: user.id)

let profile = Profile(
    user: user,
    posts: try await posts,
    followers: try await followers,
    following: try await following
)

Further Learning

For in-depth coverage of async/await patterns, error handling strategies, and real-world migration scenarios, see Swift Concurrency Course.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides comprehensive instructions and reference material for diagnosing and fixing Swift Concurrency issues. It includes detailed guidance on Swift 6 migration, actor isolation, and task management. No security risks or malicious patterns were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    1/16 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 35095d4. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 months ago
  • swift
  • concurrency
  • async-await
  • actors
  • sendable
  • main-actor
  • swift-6
  • migration
  • data-races
  • thread-safety

README badge

README badge for avdlee/swift-concurrency-agent-skill

Diagnose and fix Swift Concurrency issues including data races, isolation violations, and Sendable conformance problems. Refactors callback-based code to async/await and guides Swift 6 migration by analyzing project settings, determining isolation boundaries, and applying the smallest safe changes.

Generated from the current SKILL.md.

Does this skill help with Swift 6 migration?
Yes. The skill guides Swift 6 migration by analyzing language mode and strict concurrency settings from Package.swift or .pbxproj, then walking through diagnostics related to tasks, actors, @MainActor, Sendable, and data races.
What project settings does this skill check?
The skill analyzes Swift language mode, strict concurrency level, default isolation, and upcoming features from both SwiftPM (Package.swift) and Xcode (.pbxproj) to determine the correct fix for each diagnostic.
Does this skill refactor callback-based code to async/await?
Yes. The skill helps refactor legacy callback patterns to async/await, and routes to references/migration.md for detailed closure-to-async conversion strategies.
Will this skill recommend @MainActor as a blanket fix?
No. The skill requires justification for @MainActor isolation and avoids recommending it unless the code is truly UI-bound; it also favors structured concurrency over unstructured tasks.
Does this skill handle Core Data concurrency issues?
Yes. The skill addresses NSManagedObject sendability warnings and crossing context/actor boundaries, with routes to references/core-data.md for detailed solutions.

Generated from the current SKILL.md. These answers refresh after source changes.