Lesson 94Advanced Topics
Async/Await Basics
Modern Concurrency
Swift's async/await syntax makes asynchronous code look like synchronous code. No more callback pyramids or completion handlers!
Keywords
async
Marks function as asynchronous
func fetch() async -> Data
await
Pauses until result ready
let data = await fetch()
async let
Start parallel tasks
async let a = fetch()
Task { }
Bridge sync → async
Task { await load() }
Sequential vs Parallel
Sequential
let a = await fetchA() let b = await fetchB() // Total: timeA + timeB
Parallel
async let a = fetchA() async let b = fetchB() await (a, b) // Total: max(timeA, timeB)
Error Handling
func fetch() async throws -> Data
do {
let data = try await fetch()
} catch { ... }
do {
let data = try await fetch()
} catch { ... }
Task Cancellation
Task.checkCancellation()- Throws if cancelledTask.isCancelled- Check manuallytask.cancel()- Request cancellation
main.swift
// ASYNC/AWAIT BASICS
// Modern Swift concurrency - cleaner async code
// ===== ASYNC FUNCTIONS =====
// Mark functions that can pause with 'async'
func fetchUserData() async -> String {
// Simulate network delay
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "User data loaded"
}
// Call async functions with 'await'
func loadData() async {
let data = await fetchUserData()
print(data)
}
// ===== ASYNC THROWS =====
enum NetworkError: Error {
case badConnection
case notFound
}
func fetchUser(id: Int) async throws -> String {
try await Task.sleep(nanoseconds: 500_000_000)
if id <= 0 {
throw NetworkError.notFound
}
return "User \(id)"
}
// Call with try await
func getUser() async {
do {
let user = try await fetchUser(id: 1)
print("Got: \(user)")
} catch {
print("Error: \(error)")
}
}
// ===== SEQUENTIAL VS PARALLEL =====
// Sequential - one after another
func loadSequential() async {
let user = await fetchUserData()
let posts = await fetchPosts() // Waits for user first
print("\(user), \(posts)")
}
func fetchPosts() async -> String {
try? await Task.sleep(nanoseconds: 500_000_000)
return "Posts loaded"
}
// Parallel - simultaneously
func loadParallel() async {
async let user = fetchUserData()
async let posts = fetchPosts()
// Both start immediately, await collects results
let results = await (user, posts)
print("\(results.0), \(results.1)")
}
// ===== TASK =====
// Run async code from sync context
func startLoading() {
Task {
await loadData()
}
}
// Task with priority
func startPriorityTask() {
Task(priority: .high) {
await loadData()
}
}
// ===== TASK CANCELLATION =====
func cancellableTask() async throws -> String {
for i in 1...10 {
// Check if cancelled
try Task.checkCancellation()
// Or check manually
if Task.isCancelled {
return "Cancelled"
}
try await Task.sleep(nanoseconds: 100_000_000)
print("Step \(i)")
}
return "Completed"
}
// Cancel a task
var task: Task<String, Error>?
func startTask() {
task = Task {
try await cancellableTask()
}
}
func cancelTask() {
task?.cancel()
}
// ===== ASYNC SEQUENCES =====
func countdown() async {
for await number in AsyncStream<Int> { continuation in
for i in (1...5).reversed() {
continuation.yield(i)
}
continuation.finish()
} {
print(number)
}
}
// ===== CONVERTING COMPLETION HANDLERS =====
// Old callback-based API
func fetchDataOld(completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion("Data")
}
}
// Wrap with async/await
func fetchDataAsync() async -> String {
await withCheckedContinuation { continuation in
fetchDataOld { data in
continuation.resume(returning: data)
}
}
}
// For throwing version
func fetchDataAsyncThrows() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
// continuation.resume(throwing: error)
// continuation.resume(returning: value)
continuation.resume(returning: "Data")
}
}
// ===== @MAIN AND ASYNC =====
// Can use async in main
@main
struct MyApp {
static func main() async {
await loadData()
}
}Try It Yourself!
Create an async function that fetches weather data for 3 cities in parallel and returns the combined results!