ios-swift-concurrency

iOS - Swift Concurrency

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ios-swift-concurrency" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-ios-swift-concurrency

iOS - Swift Concurrency

Modern concurrency patterns using async/await, actors, and structured concurrency in Swift.

Key Concepts

Async/Await Fundamentals

// Async function declaration func fetchUser(id: String) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! let (data, response) = try await URLSession.shared.data(from: url)

guard let httpResponse = response as? HTTPURLResponse,
      httpResponse.statusCode == 200 else {
    throw APIError.invalidResponse
}

return try JSONDecoder().decode(User.self, from: data)

}

// Calling async functions func loadUserProfile() async { do { let user = try await fetchUser(id: "123") await MainActor.run { updateUI(with: user) } } catch { await MainActor.run { showError(error) } } }

Task Management

class UserViewController: UIViewController { private var loadTask: Task<Void, Never>?

override func viewDidLoad() {
    super.viewDidLoad()

    // Create a task for async work
    loadTask = Task {
        await loadUserData()
    }
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    // Cancel when view disappears
    loadTask?.cancel()
}

private func loadUserData() async {
    // Check for cancellation
    guard !Task.isCancelled else { return }

    do {
        let user = try await fetchUser()

        // Check again before UI update
        guard !Task.isCancelled else { return }

        await MainActor.run {
            displayUser(user)
        }
    } catch {
        // Handle error
    }
}

}

Actors for Thread Safety

actor UserCache { private var cache: [String: User] = [:]

func user(for id: String) -> User? {
    cache[id]
}

func setUser(_ user: User, for id: String) {
    cache[id] = user
}

func clear() {
    cache.removeAll()
}

}

// Usage let cache = UserCache()

Task { await cache.setUser(user, for: user.id) let cached = await cache.user(for: "123") }

MainActor for UI Updates

@MainActor class UserViewModel: ObservableObject { @Published var user: User? @Published var isLoading = false @Published var error: Error?

func loadUser(id: String) async {
    isLoading = true
    defer { isLoading = false }

    do {
        user = try await userService.fetchUser(id: id)
    } catch {
        self.error = error
    }
}

}

// Or use MainActor.run for specific operations func fetchAndDisplay() async { let data = await fetchData()

await MainActor.run {
    self.displayData(data)
}

}

Best Practices

Structured Concurrency with TaskGroup

func fetchAllUserData(userId: String) async throws -> UserProfile { async let user = fetchUser(id: userId) async let posts = fetchPosts(userId: userId) async let followers = fetchFollowers(userId: userId)

// All three requests run concurrently
return try await UserProfile(
    user: user,
    posts: posts,
    followers: followers
)

}

// For dynamic number of tasks func fetchMultipleUsers(ids: [String]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in for id in ids { group.addTask { try await fetchUser(id: id) } }

    var users: [User] = []
    for try await user in group {
        users.append(user)
    }
    return users
}

}

AsyncSequence for Streams

// Custom async sequence struct NotificationStream: AsyncSequence { typealias Element = Notification

let name: Notification.Name

struct AsyncIterator: AsyncIteratorProtocol {
    let name: Notification.Name
    var iterator: AsyncStream&#x3C;Notification>.Iterator

    mutating func next() async -> Notification? {
        await iterator.next()
    }
}

func makeAsyncIterator() -> AsyncIterator {
    let stream = AsyncStream&#x3C;Notification> { continuation in
        let observer = NotificationCenter.default.addObserver(
            forName: name,
            object: nil,
            queue: nil
        ) { notification in
            continuation.yield(notification)
        }

        continuation.onTermination = { _ in
            NotificationCenter.default.removeObserver(observer)
        }
    }

    return AsyncIterator(name: name, iterator: stream.makeAsyncIterator())
}

}

// Usage for await notification in NotificationStream(name: .userDidLogin) { handleLogin(notification) }

Continuations for Callback-Based APIs

func fetchLegacyData() async throws -> Data { try await withCheckedThrowingContinuation { continuation in legacyAPI.fetch { result in switch result { case .success(let data): continuation.resume(returning: data) case .failure(let error): continuation.resume(throwing: error) } } } }

// For delegate-based APIs class LocationManager: NSObject, CLLocationManagerDelegate { private var locationContinuation: CheckedContinuation<CLLocation, Error>?

func getCurrentLocation() async throws -> CLLocation {
    try await withCheckedThrowingContinuation { continuation in
        self.locationContinuation = continuation
        locationManager.requestLocation()
    }
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    locationContinuation?.resume(returning: locations[0])
    locationContinuation = nil
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    locationContinuation?.resume(throwing: error)
    locationContinuation = nil
}

}

Common Patterns

Cancellation Handling

func downloadFile(url: URL) async throws -> Data { var data = Data()

let (bytes, _) = try await URLSession.shared.bytes(from: url)

for try await byte in bytes {
    // Cooperative cancellation check
    try Task.checkCancellation()
    data.append(byte)
}

return data

}

Debouncing with Task

class SearchViewModel: ObservableObject { @Published var searchText = "" @Published var results: [SearchResult] = []

private var searchTask: Task&#x3C;Void, Never>?

func search(_ query: String) {
    searchTask?.cancel()

    searchTask = Task {
        // Debounce delay
        try? await Task.sleep(for: .milliseconds(300))

        guard !Task.isCancelled else { return }

        do {
            let results = try await searchService.search(query: query)
            guard !Task.isCancelled else { return }

            await MainActor.run {
                self.results = results
            }
        } catch {
            // Handle error
        }
    }
}

}

Combine Integration

import Combine

extension Publisher { func asyncMap<T>(_ transform: @escaping (Output) async -> T) -> AnyPublisher<T, Failure> { flatMap { value in Future { promise in Task { let result = await transform(value) promise(.success(result)) } } } .eraseToAnyPublisher() } }

// Convert async function to publisher func userPublisher(id: String) -> AnyPublisher<User, Error> { Future { promise in Task { do { let user = try await fetchUser(id: id) promise(.success(user)) } catch { promise(.failure(error)) } } } .eraseToAnyPublisher() }

Anti-Patterns

Blocking the Main Thread

Bad:

// DON'T do this func loadData() { let semaphore = DispatchSemaphore(value: 0) Task { data = await fetchData() semaphore.signal() } semaphore.wait() // Blocks main thread! }

Good:

func loadData() async { data = await fetchData() }

Ignoring Cancellation

Bad:

func processItems(_ items: [Item]) async { for item in items { await process(item) // Never checks cancellation } }

Good:

func processItems(_ items: [Item]) async throws { for item in items { try Task.checkCancellation() await process(item) } }

Data Races with Shared Mutable State

Bad:

class Counter { var count = 0 // Not thread-safe!

func increment() {
    count += 1
}

}

Good:

actor Counter { var count = 0

func increment() {
    count += 1
}

}

Related Skills

  • ios-swiftui-patterns: Using concurrency with SwiftUI

  • ios-uikit-architecture: Async patterns in UIKit

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review