Irbisa · Cheatsheet
Swift Concurrency
async/await, Task, actors, structured concurrency, AsyncSequence
What is the difference between concurrency and parallelism, and between synchronous and asynchronous execution?
What is GCD, and what is the difference between serial and concurrent queues and between
syncandasyncdispatch?let serial = DispatchQueue(label: "com.app.cache") // serial by defaultlet concurrent = DispatchQueue(label: "com.app.io", attributes: .concurrent) serial.async { print("1") } // returns immediatelyserial.async { print("2") } // always runs after "1"…How does
async/awaitwork in Swift, and how does it differ from completion handlers?// Before: completion handlerfunc loadUser(id: String, completion: @escaping (Result<User, Error>) -> Void) { // ...} // After: async/await…How do you call an
asyncfunction from synchronous code, and can you call synchronous code from anasyncfunction?@MainActor@Observablefinal class ProfileViewModel { var profile: Profile? private var loadTask: Task<Void, Never>?…What does this code print when
test()runs on the main thread, and what changes if it runs on a background queue?func test() { print("A") DispatchQueue.main.async { print("B") } print("C") DispatchQueue.main.sync { print("D") } print("E")}test() // called on the main threadWhat are the Quality of Service classes, and how do they affect scheduling?
What is
DispatchGroup, and when do you use it?func loadAll(ids: [String], completion: @escaping ([User]) -> Void) { let group = DispatchGroup() let lock = NSLock() var users: [User] = [] for id in ids {…What is the difference between GCD and
OperationQueue, and when would you pickOperationQueue?What is the difference between a race condition, a data race and a deadlock, and how do you prevent each?
What is a semaphore, how does it differ from a mutex, and when is
DispatchSemaphorea bad idea?import Synchronization // Mutex: one thread at a time, and the owner unlocksfinal class Counter: Sendable { private let value = Mutex(0) func increment() { value.withLock { $0 += 1 } }…What is structured concurrency, and when do you use
async letversus a task group?// Fixed fan-out: three different requests in parallelfunc loadDashboard() async throws -> Dashboard { async let user = api.user() async let posts = api.posts() // all three start right here async let news = api.news() return try await Dashboard(user: user, posts: posts, news: news)…What is the difference between
Task { }andTask.detached, and how is a task’s priority decided?@MainActor@Observablefinal class FeedModel { var items: [Item] = [] func refresh() {…How does task cancellation work in Swift Concurrency?
func processAll(_ items: [Item]) async throws { for item in items { try Task.checkCancellation() // stop between units of work try await process(item) }}…Find the bug: after the screen calls
stop(), the ticker keeps hitting the API and CPU usage jumps.// api: a Sendable global whose fetchPrice() is async throws -> Decimal@MainActorfinal class PriceTicker { private var task: Task<Void, Never>? private(set) var price: Decimal = 0 func start() { task = Task { while true { if let p = try? await api.fetchPrice() { price = p } try? await Task.sleep(for: .seconds(5)) } } } func stop() { task?.cancel() }}loadUser()throws after 0.1 s whileloadPosts()takes 10 s: what happens to the posts request, and when doesloadScreen()return?func loadScreen() async throws -> Screen { async let posts = api.loadPosts() // takes 10 s async let user = api.loadUser() // throws after 0.1 s let u = try await user let p = try await posts return Screen(user: u, posts: p)}You need to download 500 images but no more than 5 at a time: how do you cap concurrency in a task group?
func downloadAll(_ urls: [URL], maxConcurrent: Int = 5) async throws -> [URL: Data] { try await withThrowingTaskGroup(of: (URL, Data).self) { group in var pending = urls.makeIterator() var results: [URL: Data] = [:] // Seed the first `maxConcurrent` downloads…How do you add a timeout to an
awaitcall when the API has no timeout parameter?struct TimeoutError: Error {} func withTimeout<T: Sendable>( _ duration: Duration, operation: @escaping @Sendable () async throws -> T) async throws -> T {…What is an actor, how does it prevent data races, and why do you
awaitits methods from outside?actor BankAccount { let id: UUID private(set) var balance: Decimal = 0 init(id: UUID) { self.id = id }…What does
@MainActorguarantee, why not mark everything with it, and how do you move work off it?@MainActor@Observablefinal class FeedViewModel { var items: [Item] = [] func reload() async throws {…What is
Sendable, and which types conform to it automatically?import Synchronization struct User: Sendable { // all stored properties are Sendable let id: UUID var name: String}…How do you wrap a completion-handler API in
async/await, and what is the difference between checked and unsafe continuations?// Legacy APIfunc fetchUser(id: String, completion: @escaping (Result<User, Error>) -> Void) { // ...} // Async wrapper…What are
AsyncSequenceandAsyncStream, and when do you use them?// LocationTracker: a Sendable wrapper around a delegate-based APIfunc locationUpdates() -> AsyncStream<CLLocation> { AsyncStream(bufferingPolicy: .bufferingNewest(1)) { continuation in let tracker = LocationTracker() tracker.onUpdate = { continuation.yield($0) } continuation.onTermination = { _ in tracker.stop() } // cancelled or finished…This actor still downloads the same image twice when two views ask for it at once: why, and how do you fix it?
// downloader: a Sendable global dependency whose fetch(_:) is asyncactor ImageCache { private var cache: [URL: UIImage] = [:] func image(for url: URL) async throws -> UIImage { if let cached = cache[url] { return cached } let image = try await downloader.fetch(url) // suspension point cache[url] = image return image }}What is the cooperative thread pool, and why is blocking it with
DispatchSemaphore.wait()orThread.sleepa bug?// wrong: called from async code, this blocks a pool thread; one per core and the app is wedgedfunc loadSync() -> Data { let semaphore = DispatchSemaphore(value: 0) var result = Data() Task { result = (try? await api.download()) ?? Data()…How do you protect shared mutable state (actor, lock or serial queue) and how do you choose between them?
import Synchronization // Hot, synchronous, tiny critical section: a lockfinal class HitCounter: Sendable { private let counts = Mutex<[String: Int]>([:]) func record(_ key: String) { counts.withLock { $0[key, default: 0] += 1 } }…