Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 Swift

WWDC21 · 39 min · Swift

Swift concurrency: Behind the scenes

Dive into the details of Swift concurrency and discover how Swift provides greater safety from data races and thread explosion while simultaneously improving performance. We’ll explore how Swift tasks differ from Grand Central Dispatch, how the new cooperative threading model works, and how to ensure the best performance for your apps. To get the most out of this session, we recommend first watching “Meet async/await in Swift,” “Explore structured concurrency in Swift,” and “Protect mutable state with Swift actors.”

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 5 snippets

GCD code with hidden performance pitfalls swift · at 4:57 ↗
func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) { /* ... */ }

let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: concurrentQueue)

for feed in feedsToUpdate {
    let dataTask = urlSession.dataTask(with: feed.url) { data, response, error in
        // ...
        guard let data = data else { return }
        do {
            let articles = try deserializeArticles(from: data)
            databaseQueue.sync {
                updateDatabase(with: articles, for: feed)
            }
        } catch { /* ... */ }
    }
    dataTask.resume()
}
Swift concurrency equivalent using a task group swift · at 13:18 ↗
func deserializeArticles(from data: Data) throws -> [Article] { /* ... */ }
func updateDatabase(with articles: [Article], for feed: Feed) async { /* ... */ }

await withThrowingTaskGroup(of: [Article].self) { group in
    for feed in feedsToUpdate {
        group.async {
            let (data, response) = try await URLSession.shared.data(from: feed.url)
            // ...
            let articles = try deserializeArticles(from: data)
            await updateDatabase(with: articles, for: feed)
            return articles
        }
    }
}
Async functions: stack frames and async frames swift · at 15:16 ↗
// on Database
func save(_ newArticles: [Article], for feed: Feed) async throws -> [ID] { /* ... */ }

// on Feed
func add(_ newArticles: [Article]) async throws {
    let ids = try await database.save(newArticles, for: self)
    for (id, article) in zip(ids, newArticles) {
        articles[id] = article
    }
}

func updateDatabase(with articles: [Article], for feed: Feed) async throws {
    // skip old articles ...
    try await feed.add(articles)
}
Excessive context switching due to Main actor hoppping swift · at 37:13 ↗
// on database actor
func loadArticle(with id: ID) async throws -> Article { /* ... */ }

@MainActor func updateUI(for article: Article) async { /* ... */ }

@MainActor func updateArticles(for ids: [ID]) async throws {
    for id in ids {
        let article = try await database.loadArticle(with: id)
        await updateUI(for: article)
    }
}
Batch UI work to reduce the number of context switches swift · at 38:18 ↗
// on database actor
func loadArticles(with ids: [ID]) async throws -> [Article]

@MainActor func updateUI(for articles: [Article]) async

@MainActor func updateArticles(for ids: [ID]) async throws {
    let articles = try await database.loadArticles(with: ids)
    await updateUI(for: articles)
}

Resources