Grand Central Dispatch (GCD) is a great technology provided by Apple. It provides an elegant level of abstraction to work with threads, queues, and locks. And it went through a much needed make-over in Swift 3. In this post, I would like to take this one step further using enums as a queue factory.
Show Me the Code!
Below is an enum that lists the queues across your app, along with a function to provide the created, configured queue:
enum Queue: String { private static var pools = [String: DispatchQueue]() case database = "io.zamzam.databaseQueue" case transform = "io.zamzam.transformQueue" case network = "io.zamzam.networkQueue" /** Create dispatch queue. */ func create() -> DispatchQueue { let qos: DispatchQoS = self == .database ? .utility : self == .transform ? .userInitiated : self == .network ? .userInitiated : .background return DispatchQueue( label: rawValue, attributes: [.serial, qos] ) } /** Submits a block for asynchronous execution on a dispatch queue. */ func async(execute: DispatchWorkItem) { let queue = Queue.pools[rawValue] ?? { Queue.pools[rawValue] = $0 return $0 }(create()) queue.async(execute) } }
Short and sweet! The enum cases represent the labels of your dispatch queues so they can be run on separate threads. Since DispatchQueue labels do not have any correlation to queue instances, I’m reusing the same queue instance for each enum case by storing it in a dictionary.
Then in the “async” function, it creates the queue and configures it with the appropriate Quality of Service (QOS), or uses the existing queue instance from memory.
To use it, you simply do the following:
Queue.database.async { // Do my thread-based work } Queue.transform.async { // Do my thread-based work } Queue.network.async { // Do my thread-based work }
This helps architect your app into subsystems, with each subsystem represented by an enum case and backed by its own dispatch queue with the appropriate quality of service.
Happy Coding!!
Nice article. I think this is good idea to move queue related code in predefined configurations. But i don’t think that enum is really good for that, you implemented only one ‘async method’ and lost all other ‘sync’ ‘asyncAfter’
Will it be better to move queue label\qos code in seporate enum and name it QueueConfig and implement convenience init on DispatchQueue to accept that config
Also after that you can create static constants on DispatchQueue extension and get same syntax but with all queue methods
struct QueueConfig {
enum QueueID: String {
case database = “io.zamzam.databaseQueue”
case transform = “io.zamzam.transformQueue”
case network = “io.zamzam.networkQueue”
}
let queueID: QueueID
let qos: DispatchQoS
let serial: Bool
init(queueID: QueueID, qos: DispatchQoS = .default, serial: Bool = true) {
self.queueID = queueID
self.qos = qos
self.serial = serial
}
}
extension DispatchQueue {
@nonobjc static let database: DispatchQueue = DispatchQueue(config: QueueConfig(queueID: .database, qos: .utility))
@nonobjc static let transform: DispatchQueue = DispatchQueue(config: QueueConfig(queueID: .transform, qos: .userInitiated))
@nonobjc static let network: DispatchQueue = DispatchQueue(config: QueueConfig(queueID: .network, qos: .userInitiated))
}
extension DispatchQueue {
convenience init(config: QueueConfig) {
self.init(
label: config.queueID.rawValue,
qos: config.qos,
attributes: config.serial ? [] : [.concurrent]
)
}
}
DispatchQueue.database.async {}
Very nice, I like that idea! It retains the original API’s forward while still giving you the tailored API for your app ?
Thanks for sharing!
Love that one, thanks for sharing, Anton!
That is very nice. I like it.
The original example with the most recent Swift 3 syntax:
enum Queue: String {
case database = “io.zamzam.databaseQueue”
case transform = “io.zamzam.transformQueue”
case network = “io.zamzam.networkQueue”
/**
Create dispatch queue.
*/
func async(execute: DispatchWorkItem) {
let qos: DispatchQoS =
self == .database ? .utility
: self == .transform ? .userInitiated
: self == .network ? .userInitiated
: .background
let queue = DispatchQueue(label: self.rawValue, qos: qos,
attributes: [.concurrent])
queue.async(execute: execute)
}
}