Swift clip: Dispatch queues
In the second episode of Swift Clips, we’ll take a look at the DispatchQueue
API, and how we can use it to write concurrent and asynchronous code in Swift.

Genius Scan SDK: Add a powerful document scanner to any iOS app. Genius Scan’s SDK features advanced image processing that’s the result of over 10 years of research and development and can be fully customized and integrated into your app with just one line of code. Mention Swift by Sundell when requesting a free demo to get a 20% discount on your license for a whole year.
Sample code
Creating different types of dispatch queues:
let mainQueue = DispatchQueue.main
let globalQueue = DispatchQueue.global()
let customQueue = DispatchQueue(
label: "com.myapp.queue"
)
let backgroundQueue = DispatchQueue(
label: "com.myapp.queue.background",
qos: .background
)
let concurrentQueue = DispatchQueue(
label: "com.myapp.queue.concurrent",
attributes: .concurrent
)
An example of performing JSON decoding away from the main thread, using a custom queue:
extension Data {
func decoded<T: Decodable>(
as type: T.Type = T.self,
handler: @escaping (Result<T, Error>) -> Void
) {
let queue = DispatchQueue(label: "com.myapp.decoding")
let decoder = JSONDecoder()
queue.async {
let result = Result {
try decoder.decode(T.self, from: self)
}
handler(result)
}
}
}
One problem with the above is that the handler
closure will be called on our internal, custom queue — which will be problematic when used from within our UI code (as UI updates must be performed from the app’s main queue):
private extension MessageViewController {
func handleLoadedJSONData(_ data: Data) {
data.decoded(as: Message.self) { [weak self] result in
do {
try self?.showMessageView(for: result.get())
} catch {
self?.showErrorView(for: error)
}
}
}
}
To fix the above issue in a way that doesn’t require us to always handle our JSON decoding results on the main queue, we can enable a resultQueue
to be optionally injected when calling our decoded
method:
extension Data {
func decoded<T: Decodable>(
as type: T.Type = T.self,
handledOn resultQueue: DispatchQueue = .main,
handler: @escaping (Result<T, Error>) -> Void
) {
let queue = DispatchQueue(label: "com.myapp.decoding")
let decoder = JSONDecoder()
queue.async {
let result = Result {
try decoder.decode(T.self, from: self)
}
resultQueue.async { handler(result) }
}
}
}
Finally, work items enable us to submit work onto a DispatchQueue
which can later be cancelled — which is useful when implementing things like request debouncing:
class SearchResultsLoader {
private let debounceInterval: TimeInterval
private var pendingRequestWorkItem: DispatchWorkItem?
...
func performSearch(for query: String) {
pendingRequestWorkItem?.cancel()
let requestWorkItem = DispatchWorkItem {
// Perform the request
...
}
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(
deadline: .now() + debounceInterval,
execute: requestWorkItem
)
}
}