GCD
Thread -> GCD -> Operation + OperationQueue(life cycle, dependencies between different queues, cancel)
[Sync vs Async]
[iOS Thread safe]
Grand Central Dispatch(GCD)
libdispatch
operates on dispatch queues DispatchQueue
. It works by.first in first out(FIFO) order
DispatchQueue.<queue>.<sync/async>
means run a <sync/async>
task on the <queue>
Queue can be serial or concurrent. Concurrent(it is more parallel) allows to process with several task simultaneously while serial - one after another. Concurrent tasks are started at that order which they were added but can be finished at different order
GCD
supports:
main queue
- serial queue on a main thread of app which is used to working with UI
global queue
- concurrent queues which are shared between whole iOS operation system
private queue
- serial/concurrent queues in app scope
Main Queue
//Thread.current.qualityOfService = .userInitiated
DispatchQueue.main
Global Queues
DispatchQueue.global(qos: DispatchQoS.QoSClass = .default)
Private Queue
DispatchQueue(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
//Serial Queue
DispatchQueue(label: "first.myApp.com")
//Concurrent Queue - attributes: .concurrent
DispatchQueue(label: "second.myApp.com", attributes: .concurrent)
Quality of Service(QoS)
Every Queue has qos which determines a priority of it
userInteractive
- the highest priority - speed over energy. Task result of which are reflected on UI. Task should be small to produce fast result. For example animation
userInitiated
- task which is started by user and result of which is async. For example loading data/opening local file to show result on UI
utility
- long-running calculations up to several minutes like saving data to file...
background
- the lowest priority - energy over speed. Very long-running computations from several minutes - like sync data.
Additional QoS which you should avoid:
- default - priority is between .userInitiated and .utility
- unspecified - uses a QoS of outer thread(inherits the QoS of the caller). For example when you call it from main thread
thread.qualityOfService == .userInitiated
func examineQoS(qos: DispatchQoS) {
let queue = <create_queue>
queue.async {
print("""
QoS
qos: \(qos.qosClass)
queue.qos: \(queue.qos.qosClass)
thread.qualityOfService: \(Thread.current.qualityOfService)
""")
}
}
let queue = DispatchQueue.global()
let queue = DispatchQueue(label: "mySerial")
let queue = DispatchQueue(label: "myConcurrent", attributes: .concurrent)
//qos == .default
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated
let queue = DispatchQueue.global(qos: qos.qosClass)
//qos: default
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated
//qos: unspecified
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated
//Others
//qos == queue.qos == thread.qualityOfService
let queue = DispatchQueue(label: "mySerial", qos: qos)
let queue = DispatchQueue(label: "myConcurrent", qos: qos, attributes: .concurrent)
//qos: default
//queue.qos: default
//thread.qualityOfService: .default
//qos: unspecified
//queue.qos: unspecified
//thread.qualityOfService: .userInitiated
//Others
//qos == queue.qos == thread.qualityOfService
- Count of worker threads are depended on OS conditions. There are no
run loop
[About] for worker thread.
sync/async
sync
- block a current thread and wait when it will be finished on a specified queue
async
- do not block a current thread and send an execution block of code to the specificified queue
//deadline in seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// logic
}
Common mistake: deadlock
If you call DispatchQueue.main.sync
on a main
thread - the app will be frozen because the calling DispatchQueue.main.sync
starts waiting immediately when the dispatched block is finished (dispatched block is not started)
Some notes:
DispatchWorkItem
- delaying/cancelling/prioritise a task inside Queue
or DispatchGroup
DispatchGroup
if you are going to execute several async tasks with a single callback even on different queues. All these task should be grouped. DispatchGroup
contains thread safe counter and when it equals 0 notify
is called
//create group
let group = DispatchGroup()
//case 1
DispatchQueue.<queue>.async(group: group) //
//case 2 - manual
group.enter() //<- +1
DispatchQueue.global().async {
//logic
group.leave() //<- -1
}
//notification
group.notify(queue: <callback_queue>) {
//logic
}
Barrier
flag inside concurrent queue for sync/async task guaranties that there is no race condition
[About]. The best place for it is custom queue because does not block any others global tasks:
customQueue.async(flags: .barrier) {
//logic
someProperty = someValue
}
- all task which were started are finished
- Single Barrier task
- Executing all other tasks in the queue
thread safe
operation can be reached through Barrier
in concurrent queue for shared variable:
- read - sync operation on concurrent queue
- write - async operation with
barrier
[Thread safe singleton]
async
means it doesn't block, but you're calling it onDispatchQueue.main
, which is a queue guaranteed to run on the main thread. – Malda