Irbisa · Cheatsheet
GCD & Threading
Queues, QoS, DispatchGroup and semaphores, the main.sync deadlock, Operation, bridging to async/await
What is
Thread(NSThread), and why do iOS apps almost never create threads by hand?On which thread does a
URLSessioncompletion handler run, and how do you update the UI from it?How do you run code after a delay with GCD, and how do you cancel it before it runs?
final class SearchViewController: UIViewController { private var pendingSearch: DispatchWorkItem? func searchTextChanged(_ text: String) { pendingSearch?.cancel() // drops the previous item if it has not started let item = DispatchWorkItem { [weak self] in…What is Thread Sanitizer, and how is it different from the Main Thread Checker?
What does this code print when
run()is called on the main thread, and on which thread does each block run?// run() is called on the main thread, for example from viewDidLoadfunc run() { let serial = DispatchQueue(label: "com.app.serial") DispatchQueue.main.async { print("A", Thread.isMainThread) } serial.sync { print("B", Thread.isMainThread) } DispatchQueue.global().sync { print("C", Thread.isMainThread) } serial.async { print("D", Thread.isMainThread) } serial.sync { print("E", Thread.isMainThread) } print("F")}How does a dispatch barrier turn a concurrent queue into a reader-writer lock, and when does the barrier flag do nothing?
final class SessionStore: @unchecked Sendable { private var tokens: [String: String] = [:] // right: a private concurrent queue. On DispatchQueue.global() the barrier flag is ignored private let queue = DispatchQueue(label: "com.app.sessions", attributes: .concurrent) func token(for user: String) -> String? {…Find the bug: this thread-safe cache crashes the first time
image(for:orInsert:)is called.final class ImageCache: @unchecked Sendable { private let queue = DispatchQueue(label: "com.app.image-cache") private var storage: [URL: UIImage] = [:] func image(for url: URL) -> UIImage? { queue.sync { storage[url] } } func image(for url: URL, orInsert make: () -> UIImage) -> UIImage { queue.sync { if let cached = image(for: url) { return cached } let image = make() storage[url] = image return image } }}Find the bug:
loadAvatarssometimes never calls its completion.// api.fetchAvatar(id:completion:) is declared with a @MainActor completion, called on the main queue@MainActorfunc loadAvatars(ids: [String], completion: @escaping @MainActor ([UIImage]) -> Void) { let group = DispatchGroup() var images: [UIImage] = [] for id in ids { group.enter() api.fetchAvatar(id: id) { result in guard case .success(let image) = result else { return } images.append(image) group.leave() } } group.notify(queue: .main) { completion(images) }}Find the bug: this retry timer is scheduled from a background queue and never fires.
final class UploadRetrier: @unchecked Sendable { private let queue = DispatchQueue(label: "com.app.upload") private var retryTimer: Timer? // Called by the upload code when a request fails func uploadDidFail() { queue.async { self.retryTimer?.invalidate() self.retryTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] _ in self?.retry() } } } private func retry() { /* start the upload again */ }}What does
DispatchQueue.concurrentPerformdo, and why is it better thanglobal().asyncin a loop for CPU-heavy work?import Synchronizationimport Dispatch func checksum(_ chunk: ArraySlice<UInt8>) -> Int { chunk.reduce(0) { $0 &+ Int($1) } } func parallelChecksum(of bytes: [UInt8]) -> Int {…Which locks do you use in Swift (
NSLock,NSRecursiveLock,OSAllocatedUnfairLock,Mutex), and why not a rawos_unfair_lock?import Foundationimport os // wrong: Swift does not guarantee a stable address for a stored struct, so &lock may be a// temporary copy; it often works by accidentfinal class UnsafeCounter: @unchecked Sendable {…Which of these are thread-safe in Swift: a
static let, alazy var, anArrayyou append to, anIntcounter?How do you chain work with
Operationdependencies, and what happens to a dependent operation when its dependency is cancelled?import Foundation final class ReadFileOperation: Operation, @unchecked Sendable { let url: URL private(set) var data: Data? init(url: URL) { self.url = url }…In a Swift 6 codebase built on async/await, when is GCD still the right tool?
How do you write an asynchronous
Operationsubclass, and why does the queue get stuck ifisFinishedis not KVO-compliant?import Foundation class AsyncOperation: Operation, @unchecked Sendable { private let lock = NSLock() private var _executing = false private var _finished = false…What QoS does a block submitted with
asyncrun at, and how does GCD resolve priority inversion (and when can it not)?Instruments shows 60+ threads because every object has its own serial queue. Why does that happen, and how do target queues fix it?
// One serial queue per subsystem; every per-object queue funnels into itlet databaseQueue = DispatchQueue(label: "com.app.database", qos: .utility) final class Table: @unchecked Sendable { private let queue: DispatchQueue private var rows: [String] = []…Is
DispatchQueue.main.asyncthe same as running on@MainActor, and what changes when you migrate GCD code to Swift Concurrency?nonisolated func didFetch(_ name: String, into label: UILabel) { // A closure passed literally to DispatchQueue.main.async is treated as @MainActor DispatchQueue.main.async { label.text = name } // Through a variable it is a plain @Sendable closure, and touching label is diagnosed: // let queue = DispatchQueue.main; queue.async { label.text = name }…