正在加载,请稍候…

Swift Concurrency in Practice: async/await, Actors, and Structured Concurrency

Master Swift Concurrency with async/await, the Actor model for data isolation, structured concurrency with task groups, and graceful task cancellation patterns.

Swift Concurrency in Practice: async/await, Actors, and Structured Concurrency

Swift Concurrency, introduced in Swift 5.5 and significantly enhanced through Swift 6, provides a safe and expressive model for writing concurrent code. Gone are the days of callback pyramids and DispatchQueue juggling.

The Basics: async/await

Defining and Calling Async Functions

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)
}

Bridging from Synchronous Code

override func viewDidLoad() {
    super.viewDidLoad()
    Task {
        await loadProfile()
    }
}

func fetchImageFromLegacyAPI(_ url: URL) async throws -> UIImage {
    try await withCheckedThrowingContinuation { continuation in
        legacyImageLoader.load(url: url) { result in
            switch result {
            case .success(let image): continuation.resume(returning: image)
            case .failure(let error): continuation.resume(throwing: error)
            }
        }
    }
}

Actor Model: Safe Shared State

Actors prevent data races at compile time by isolating mutable state.

actor UserCache {
    private var cache: [String: User] = [:]
    private var fetchTasks: [String: Task<User, Error>] = [:]

    func user(for id: String) async throws -> User {
        if let cached = cache[id] { return cached }

        if let existingTask = fetchTasks[id] {
            return try await existingTask.value
        }

        let task = Task<User, Error> {
            try await APIClient.shared.fetchUser(id: id)
        }

        fetchTasks[id] = task

        do {
            let user = try await task.value
            cache[id] = user
            fetchTasks.removeValue(forKey: id)
            return user
        } catch {
            fetchTasks.removeValue(forKey: id)
            throw error
        }
    }

    func invalidate(id: String) { cache.removeValue(forKey: id) }
}

@MainActor

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

    private let userCache = UserCache()

    func load(userId: String) async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await userCache.user(for: userId)
        } catch {
            self.error = error
        }
    }
}

Structured Concurrency

Concurrent Async Let

func fetchAllUserData(userId: String) async throws -> UserDashboard {
    async let profile = APIClient.shared.fetchProfile(userId: userId)
    async let posts = APIClient.shared.fetchPosts(userId: userId)
    async let followers = APIClient.shared.fetchFollowers(userId: userId)

    return try await UserDashboard(
        profile: profile,
        posts: posts,
        followers: followers
    )
}

TaskGroup for Dynamic Work

func downloadImages(urls: [URL]) async throws -> [URL: UIImage] {
    try await withThrowingTaskGroup(of: (URL, UIImage).self) { group in
        for url in urls {
            group.addTask {
                let image = try await ImageLoader.shared.load(url: url)
                return (url, image)
            }
        }

        var results: [URL: UIImage] = [:]
        for try await (url, image) in group {
            results[url] = image
        }
        return results
    }
}

Task Cancellation

Cooperative Cancellation

func processLargeDataset(_ items: [DataItem]) async throws -> [ProcessedItem] {
    var results: [ProcessedItem] = []

    for (index, item) in items.enumerated() {
        try Task.checkCancellation()

        let processed = try await processItem(item)
        results.append(processed)

        if index % 100 == 0 {
            await Task.yield()
        }
    }

    return results
}

Cancellable View Tasks in SwiftUI

struct SearchView: View {
    @State private var query = ""
    @State private var results: [SearchResult] = []

    var body: some View {
        VStack {
            SearchBar(text: $query)
            ResultsList(results: results)
        }
        .task(id: query) {
            guard !query.isEmpty else { results = []; return }

            do {
                try await Task.sleep(for: .milliseconds(300))
                results = try await SearchService.search(query: query)
            } catch is CancellationError {
                // Ignore cancellation
            } catch {
                print("Search error:", error)
            }
        }
    }
}

AsyncSequence

func processEvents() async {
    let stream = AsyncStream<WebSocketMessage> { continuation in
        webSocket.onMessage = { message in
            continuation.yield(message)
        }
        webSocket.onClose = {
            continuation.finish()
        }
    }

    for await message in stream {
        await handleMessage(message)
    }
}

Swift 6 Strict Concurrency Checking

// Sendable conformance
struct UserData: Sendable {
    let id: String
    let name: String
}

// @unchecked Sendable for manual safety guarantees
final class ThreadSafeCounter: @unchecked Sendable {
    private var count = 0
    private let lock = NSLock()

    func increment() {
        lock.withLock { count += 1 }
    }

    var value: Int { lock.withLock { count } }
}

Performance Patterns

Limit concurrency to avoid overwhelming the system:

func batchProcess<T>(items: [T], maxConcurrency: Int = 4) async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        var inFlight = 0

        for item in items {
            if inFlight >= maxConcurrency {
                try await group.next()
                inFlight -= 1
            }

            group.addTask { try await processItem(item) }
            inFlight += 1
        }

        try await group.waitForAll()
    }
}

Conclusion

Swift Concurrency transforms iOS and macOS development. The async/await syntax eliminates callback pyramids, actors prevent data races at compile time, and structured concurrency makes parallel work safe and readable. With Swift 6's strict checking, the compiler becomes your concurrency safety net, leading to code that is easier to reason about, safer, and often faster.