Swift – Grand Central Dispatch and Multi Threading tutorial
Before we get into coding for grand central dispatch, what is is, how to use it and why you would use it. Then we have an app starter template that you can then build on to see why you need to use it for certain iOS development tasks, such as loading an image or file. This will give you ideas how it could be used to improve other areas of development.
What is Grand Central Dispatch?
Grand Central Dispatch, also known as GCD is a way to optimize performance for processor with multiple cores and also using multi-threading which in a nutshell allows you to run tasks in parallel. To understand GCD you need you have familiarity with the following concepts:
Concurrency & Parralellism
iOS Apps are made up of one or more threads. A single core processor and multi-core processor can run multiple threads, just in different manners, the following image from www.alvinalexander.com outlines this nicely:
Think of each person as a “task”. A task could be getting an image from a URL, submitting data to a server, changing screens on the UI and so on. In the above image we have two queues.
- Concurrency has to share the “Coke” machine resource between the two queues (Single core processor)
- Parallel queues are split between two “Coke” machines (Multi core processor)
GCD provides a way to interface with threads via these queues. Typically in an iOS App your code is run on the “Main” thread which is responsible for also updating the user interface. Usually this is not a problem, however when you need to download an image it also blocks the UI from being updated, its taking to long in front of the coke machine!
Queues
We manage queues through DispatchQueue which is our interface for managing queues. You can either execute the tasks in order which is a Serial queue, or along side each other which is a Concurrent queue.
Serial Queue
Concurrent Queue
GCD controls when tasks start and stop. However note that tasks can start at different times depending on the amount of threads available as in the above diagram. There are three queues GCD manages:
- Main Queue – Code by default & the UI runs on this.
- Global Queues – Any tasks you want to run concurrently are run here.
- Custom Queues – Custom queues that you can create. Can either be serial or concurrent.
In addition to assigning tasks to a queue, you can also give them a priority. The higher a priority means it will be given access to the threads to run its task before anything under it. iOS has the following priorites.
Synchronous and Asynchronous
- Synchronous is where you run all the tasks in order, much like a serial queue.
- Asynchronous is where you just hand off the task to the queue, it will run in the background and can have a callback when it is completed.
Coding our App to download images Async on a thread
Go ahead and download the starter app here. Run the app and you will notice it will eventually load a number of cat images, however very slowly (Anywhere from 30 seconds to a minute to load)
What is happening is the app will download all the images then show the UI to the user as follows on the Main queue.
Which is bad, the more images we have the longer it will take to download. And as its running on the main thread it also stops the screen responding to our input until its done!
So we are going to change it to run like this, to the images download in an async way. The Show UI will run on the Main queue, and the Download Images will run on the Global queue, so we can run it in the background.
To achieve this, in the app change the following code in collectionView cellForItemAt IndexPath function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: ImageCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ImageCollectionViewCell cell.myImage.image = #imageLiteral(resourceName: "loadingdots") let url = URL(string: tableImages[indexPath.row]) let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch cell.myImage.image = UIImage(data: data!) return cell }
to:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: ImageCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ImageCollectionViewCell cell.myImage.image = #imageLiteral(resourceName: "loadingdots") let url = URL(string: tableImages[indexPath.row]) // Download the image on a background thread, so our UI can still update DispatchQueue.global().async { let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch // Once the image is downloaded, push it back to our Main Thread that the UI runs on to show the image to the user DispatchQueue.main.async { cell.myImage.image = UIImage(data: data!) } } return cell }
You notice in our code now, we download the image using GCD queues. We download the image on the global que in an async manner. Once this is downloaded, we hand over control back to the Main UI Thread to actually show the image to the user.
Run the app now and you will notice a huge difference, the app will load straight away and within 30 seconds all the images will start showing to the user, neat!
So that covers the basics of Grand Central Dispatch, you can download the completed source code below. Be sure to subscribe for when part 2 is released, which will explore it more as we have only scratched the surface and for future tutorials.