Synchronous Task Example
@IBAction func synchronousDownload(_ sender: UIBarButtonItem) {
// Get the URL for the image
let url = URL(string: BigImages.seaLion.rawValue)
// Obtain the NSData with the image
let imgData = try? Data(contentsOf: url!)
// Turn it into a UIImage
let image = UIImage(data: imgData!)
// Display it
photoView.image = image
}
This code blocks the
MainQueue
which makes this example a weak implementation. When downloading a large image this code will block the app for a human perceivable amount of time, and will most likely get rejected from the App Store
Simple Asynchronous Task Example
// This method avoids blocking by creating a new queue that runs
// in the background, without blocking the UI.
@IBAction func simpleAsynchronousDownload(_ sender: UIBarButtonItem) {
// Get the URL for the image
let url = URL(string: BigImages.shark.rawValue)
// create a queue from scratch
let downloadQueue = DispatchQueue(label: "download", attributes: [])
// call dispatch async to send a closure to the downloads queue
downloadQueue.async { () -> Void in
// download Data
let imgData = try? Data(contentsOf: url!)
// Turn it into a UIImage
let image = UIImage(data: imgData!)
// display it
DispatchQueue.main.async(execute: { () -> Void in
self.photoView.image = image
})
}
}
Note that in almost every case we don't want to run anything from UIKit
in the background, but not all of UIKit
is thread unsafe for the background, there are a few exceptions such as UIImage
. However UIKit
updates to visual aspects like updating an image property must be done in the MainQueue
.
We adhere to best practices by creating a background queue for downloading the image with DispatchQueue(label:attributes:)
and call it with DispatchQueue.async
. From within that background closure we get a reference to the MainQueue
and then call DispatchQueue.main.async
within it to send another closure to the MainQueue
to update the UI.
Asynchronous Task Example
In this final example we create two functions, the first to handle updating the UI and kick off the image download which is executed in the second function that takes a closure as a parameter.
// This code downloads the huge image in a global queue and uses a completion closure.
@IBAction func asynchronousDownload(_ sender: UIBarButtonItem) {
// hide current image
photoView.image = nil
// start animation
activityView.startAnimating()
withBigImage { (image) -> Void in
// Display it
self.photoView.image = image
// Stop animating
self.activityView.stopAnimating()
}
}
As you can see we place all the code we get from the above function that updates the UI in the MainQueue
by calling DispatchQueue.main.async
and running our closure we get in the argument handler
.
func withBigImage(completionHandler handler: @escaping (_ image: UIImage) -> Void){
DispatchQueue.global(qos: .userInitiated).async { () -> Void in
if let url = URL(string: BigImages.whale.rawValue), let imgData = try? Data(contentsOf: url), let img = UIImage(data: imgData) {
// all set and done, run the completion closure!
DispatchQueue.main.async(execute: { () -> Void in
handler(img)
})
}
}
}
This method downloads and image in the background once it's finished, it runs the closure it receives as a parameter, also called a completion handler