ref – http://www.appcoda.com/grand-central-dispatch/
Queue
A queue is actually a block of code that can be executed synchronously or asynchronously, either on the main or on a background thread.
Once a queue is created, the operating system is the one that manages it and gives it time to be processed on any core of the CPU.
Multiple queues are managed accordingly, and that management is something that developers don’t have to deal with.
Queues are following the FIFO pattern (First In, First Out), meaning that the queue that comes first for execution will also finish first (think of it like a queue of humans waiting in front of the counter, the first one is served first, the last one is served last).
Work Item
A work item is literally a block of code that is either written along with the queue creation, or it gets assigned to a queue and it can be used more than once (reused). It’s the code that a dispatch queue will run. The execution of work items in a queue also follows the FIFO pattern.
This execution can be synchronous or asynchronous.
In the synchronous case, the running app does not exit the code block of the item until the execution finishes. Thus, when the execution is happening, the running app does not exit, and this results in the UI being frozen for a little while.
On the other hand, when queues are scheduled to run asynchronously, then the running app calls the work item block and it returns at once and continue with its main thread. Thus you will not see pauses on the UI.
Example 1
The sync will process its display of 0 to 10, and you’ll notice that you won’t be able to click on the UI button. That’s because the block of code is being processed by the queue in a sync fashion. You’ll have to wait until it completes. Then you’ll notice that your button presses will take effect.
Result:
–viewDidLoad–
button created
view hieararchy added button
— SLEEP! —
0
— SLEEP! —
1
— SLEEP! —
2
— SLEEP! —
3
— SLEEP! —
4
— SLEEP! —
5
— SLEEP! —
6
— SLEEP! —
7
— SLEEP! —
8
— SLEEP! —
9
Button Clicked
Button Clicked
Button Clicked
Button Clicked
Button Clicked
Button Clicked
Button Clicked
source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import UIKit class ViewController: UIViewController { let queue = DispatchQueue(label: "com.appcoda.myqueue") func action(selector:UIButton) { print("Button Clicked") } override func viewDidLoad() { print("--viewDidLoad--") super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let button = UIButton(type: UIButtonType.roundedRect) as UIButton button.backgroundColor = UIColor.orange button.addTarget(self, action: #selector(action), for: UIControlEvents.touchUpInside) button.frame = CGRect(x: 80, y: 60, width: 200, height: 80) print("button created") self.view.addSubview(button) print("view hieararchy added button") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) queue.sync { // provide code as closure for i in 0..<10 { sleep(1) print(" \(i)") } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |
Change sync to async
Now let’s change the sync into an async. You’ll see that your button now has effect while the tasks are being processed.
1 2 3 4 5 6 7 |
queue.async { // change sync into async for i in 0..<10 { print("-- SLEEP! --") sleep(1) print(" \(i)") } } |
Output:
–viewDidLoad–
button created
view hieararchy added button
Button Clicked
0
Button Clicked
Button Clicked
Button Clicked
1
Button Clicked
Button Clicked
2
3
Button Clicked
4
5
6
7
Button Clicked
Button Clicked
Button Clicked
Button Clicked
8
Button Clicked
9
The important here is to make clear that our main queue is free to “work” while we have another task running on the background, and this didn’t happen on the synchronous execution of the queue.
Qos – Quality of Service (enum)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
class ViewController: UIViewController { let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.userInitiated) let queue2 = DispatchQueue(label: "com.appcoda.queue2", qos: DispatchQoS.userInitiated) func action(selector:UIButton) { print("Button Clicked") } override func viewDidLoad() { print("--viewDidLoad--") super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let button = UIButton(type: UIButtonType.roundedRect) as UIButton button.backgroundColor = UIColor.orange button.addTarget(self, action: #selector(action), for: UIControlEvents.touchUpInside) button.frame = CGRect(x: 80, y: 60, width: 200, height: 80) print("button created") self.view.addSubview(button) print("view hieararchy added button") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) queue1.async { for i in 0..<10 { sleep(1) print("Á \(i)") } } queue2.async { for j in 0..<10 { sleep(1) print("Œ \(j)") } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |
output:
–viewDidLoad–
button created
view hieararchy added button
Œ 0
Á 0
Œ 1
Á 1
Á 2
Œ 2
Á 3
Œ 3
Á 4
Œ 4
Œ 5
Á 5
Œ 6
Á 6
Œ 7
Á 7
Œ 8
Á 8
Œ 9
Á 9
It’s easy to say by looking at the above screenshot that both tasks are “evenly” executed, and actually this is what we expect to happen.
Now change priority of queue2 to DispatchQoS.utility
1 2 |
let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.userInitiated) let queue2 = DispatchQueue(label: "com.appcoda.queue2", qos: DispatchQoS.utility) |
The first dispatch queue (queue1) will be executed faster than the second one, as it’s given a higher priority. Even though the queue2 gets an opportunity of execution while the first one is running, the system provides its resources mostly to the first queue as it was marked as a more important one. Once it gets finished, then the system takes care of the second queue.
Make sure you change the index i and j to 100, instead of 10. Then run it, you’ll see that are about the same…until when the index gets into the 20s. For my machine, at around index 25, 26, queue1 runs twice, which hints that queue1 gets more of attention.
Now let’s change queue1 to background. Let’s change queue2 back to userInitiated.
1 2 |
let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.background) let queue2 = DispatchQueue(label: "com.appcoda.queue2", qos: DispatchQoS.userInitiated) |
If you were to run the code again, you’ll see that queue2 (having the higher priority) will finish a full 6 indexes faster than queue1.
The common thing to all the previous examples is the fact that our queues are serial. That means that if we would assign more than one tasks to any queue, then those tasks would have been executed one after another, and not all together.
Concurrent Queues
There’s a new argument in the above initialisation: The attributes parameter. When this parameter is present with the concurrent value, then all tasks of the specific queue will be executed simultaneously. If you don’t use this parameter, then the queue is a serial one. Also, the QoS parameter is not required, and we could have omitted it in this initialisation without any problem.
We have one concurrent queue (called anotherQueue), and it runs 3 tasks. All three tasks are executed at the same time (concurrently), running through their own perspective for loops.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
class ViewController: UIViewController { // let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.userInitiated) // let queue2 = DispatchQueue(label: "com.appcoda.queue2", qos: DispatchQoS.utility) let anotherQueue = DispatchQueue(label: "com.appcoda.anotherQueue", qos: .utility, attributes: .concurrent) func action(selector:UIButton) { print("Button Clicked") } override func viewDidLoad() { print("--viewDidLoad--") super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let button = UIButton(type: UIButtonType.roundedRect) as UIButton button.backgroundColor = UIColor.orange button.addTarget(self, action: #selector(action), for: UIControlEvents.touchUpInside) button.frame = CGRect(x: 80, y: 60, width: 200, height: 80) print("button created") self.view.addSubview(button) print("view hieararchy added button") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) anotherQueue.async { for i in 0..<10 { sleep(1) print("Á \(i)") } } anotherQueue.async { for i in 0..<10 { sleep(1) print(" \(i)") } } anotherQueue.async { for i in 0..<10 { sleep(1) print("∏ \(i)") } } // queue1.async { // for i in 0..<100 { // sleep(1) // print("Á \(i)") // } // } // // // queue2.async { // for j in 0..<100 { // sleep(1) // print(" \(j)") // } // } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } |
User Activated
The attributes parameter can also accept another value named initiallyInactive. By using that, the execution of the tasks doesn’t start automatically, instead the developer has to trigger the execution.
Delaying the Execution
You can activate when you want your queue to start executing. This is done by creating a dispatch queue, then assigning a property reference to it. Once you do that, you can use the property reference to call the activate method wherever you like in your class.
1 2 3 |
if let queue = inactiveQueue { queue.activate() } |
Notice that you assign serial, concurrent, and initiallyInactive characteristics via an array on the attributes property when creating DispatchQueue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import UIKit class ViewController: UIViewController { var inactiveQueue: DispatchQueue! func action(selector:UIButton) { print("Button Clicked") } override func viewDidLoad() { print("--viewDidLoad--") super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let button = UIButton(type: UIButtonType.roundedRect) as UIButton button.backgroundColor = UIColor.orange button.addTarget(self, action: #selector(action), for: UIControlEvents.touchUpInside) button.frame = CGRect(x: 80, y: 60, width: 200, height: 80) print("button created") self.view.addSubview(button) print("view hieararchy added button") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) print(" Create an Inactive Queue") // if serial //let anotherQueue = DispatchQueue(label: "com.appcoda.anotherQueue", qos: .utility, attributes: .initiallyInactive) // if concurrent let anotherQueue = DispatchQueue(label: "com.appcoda.anotherQueue", qos: .utility, attributes: [.concurrent, .initiallyInactive]) // The use of a class property in that case is necessary, because the anotherQueue is defined // in the concurrentQueues() method and it’s visible only there. The app won’t know about // it when it’ll exit the method, we won’t be able to activate the queue, but most importantl // we’ll get a crash on runtime. print("Ò Assign out inactive Queue reference on this created queue") inactiveQueue = anotherQueue print("∏ Add some code blocks onto the queue") anotherQueue.async { for i in 0..<10 { sleep(1) print(" \(i)") } } anotherQueue.async { for i in 0..<10 { sleep(1) print("Ø \(i)") } } if let queue = inactiveQueue { print("Á property is valid. Let's fire!") queue.activate() } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |
Accessing the Main and Global Queues
In all the previous examples we manually created the dispatch queues we used. However, it’s not always necessary to do that, especially if you don’t desire to change the properties of the dispatch queue. As I have already said in the beginning of this post, the system creates a collection of background dispatch queues, also named global queues. You can freely use them like you would do with custom queues, just keep in mind not to abuse the system by trying to use as many global queues as you can.
Using or not global queues, it’s almost out of the question the fact that you’ll need to access the main queue quite often; most probably to update the UI.
Global queue let’s you run tasks in the background. Once you are ready to update the UI, do so by using the main thread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import UIKit class ViewController: UIViewController { let globalQueue = DispatchQueue.global() // There are not many properties that you can change when using global queues. // However, you can specify the Quality of Service class that you want to be used: // let globalQueue = DispatchQueue.global(qos: .userInitiated) var button : UIButton? = nil func action(selector:UIButton) { print("Button Clicked") } override func viewDidLoad() { print("--viewDidLoad--") super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.button = UIButton(type: UIButtonType.roundedRect) as UIButton self.button?.backgroundColor = UIColor.orange self.button?.addTarget(self, action: #selector(action), for: UIControlEvents.touchUpInside) self.button?.frame = CGRect(x: 80, y: 60, width: 200, height: 80) self.button?.setTitle("Push Me", for: .normal) print("button created") self.view.addSubview(self.button!) print("view hieararchy added button") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) globalQueue.async { for i in 0..<10 { if ( i==8) { DispatchQueue.main.async { self.button?.setTitle("DON'T PUSH", for: .normal) } } sleep(1) print(" - \(i)") } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |
WorkItem
A DispatchWorkItem is a block of code that can be dispatched on any queue and therefore the contained code to be executed on a background, or the main thread. Think of it really simply; as a bunch of code that you just invoke, instead of writing the code blocks in the way we’ve seen in the previous parts.
Basically A workItem is a block of code that is executed by a queue. Right before execution it will notify via the notify method any other queues. In our example, we notify the main queue to run a block of code that simply prints out the value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
import UIKit class ViewController: UIViewController { var button : UIButton? = nil func action(selector:UIButton) { print("Button Clicked") } override func viewDidLoad() { print("--viewDidLoad--") super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.button = UIButton(type: UIButtonType.roundedRect) as UIButton self.button?.backgroundColor = UIColor.orange self.button?.addTarget(self, action: #selector(action), for: UIControlEvents.touchUpInside) self.button?.frame = CGRect(x: 80, y: 60, width: 200, height: 80) self.button?.setTitle("Push Me", for: .normal) print("button created") self.view.addSubview(self.button!) print("view hieararchy added button") } func useWorkItem() { print("--> \(#function)") print("create temp var 'value' ") var value = 10 let workItem = DispatchWorkItem { print("----executing DispatchWorkItem-----") value += 5 print("value is has been changed to: \(value)") } print("DispatchWorkItem.perform()") workItem.perform() // 15 print("get global queue") let queue = DispatchQueue.global(qos: .utility) print("execute DispatchWorkItem via async") queue.async(execute: workItem) // 20 queue.async(execute: workItem) // 25 queue.async(execute: workItem) // 30 queue.async(execute: workItem) // 35 queue.async(execute: workItem) // 40 print("DispatchWorkItem notify queue via main, print out value") // we attach a block of code to run whenever workItem is being executed on the queue // the code specify that whenever workItem is being executed, we run this on the main thread workItem.notify(queue: DispatchQueue.main) { print("workItem about to be executed!, its current value is: ", value) } print("<-- \(#function)") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.useWorkItem() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |
output:
–viewDidLoad–
button created
view hieararchy added button
–> useWorkItem()
create temp var ‘value’
DispatchWorkItem.perform()
—-executing DispatchWorkItem—–
value is has been changed to: 15
get global queue
execute DispatchWorkItem via async
DispatchWorkItem notify queue via main, print out value
<-- useWorkItem()
workItem about to be executed!, its current value is: 15
----executing DispatchWorkItem-----
value is has been changed to: 20
----executing DispatchWorkItem-----
value is has been changed to: 25
----executing DispatchWorkItem-----
value is has been changed to: 30
----executing DispatchWorkItem-----
value is has been changed to: 35
----executing DispatchWorkItem-----
value is has been changed to: 40