2021 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 ↗Code shown on screen · 5 snippets
GCD code with hidden performance pitfalls
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
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
// 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
// on database actor
func loadArticle(with id: ID) async throws -> Article { /* ... */ }
func updateUI(for article: Article) async { /* ... */ }
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
// on database actor
func loadArticles(with ids: [ID]) async throws -> [Article]
func updateUI(for articles: [Article]) async
func updateArticles(for ids: [ID]) async throws {
let articles = try await database.loadArticles(with: ids)
await updateUI(for: articles)
} Resources
Related sessions
-
43 min -
24 min -
43 min -
25 min -
29 min -
34 min -
29 min -
28 min -
33 min -
1h 1m -
55 min -
36 min