Backgrounding Lengthy Tasks L4.10.1

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