Appendix C. How Asynchronous Works

Beginners sometimes don’t quite understand what it means for code to run asynchronously. Asynchronous code runs at an indefinite time. More important, it runs after the surrounding code. This means that the order in which the code appears is not the order in which it will run.

Consider the following (and see Chapter 23):

func doSomeNetworking() {
    // ... prepare url ...
    let session = URLSession.shared 1
    let task = session.downloadTask(with:url) { loc, resp, err in 2
        // ... completion function body goes here ... 4
    }
    task.resume() 3
}

The method downloadTask(with:completionHandler:) calls its completion function asynchronously. It calls it when the networking finishes — and networking takes time. The order in which the chunks of code run is the numerical order of the numbered lines:

1

The code before the call.

2

The call itself.

3

The code after the call, including the return from the surrounding function doSomeNetworking. Your code has now come to a complete stop!

4

The code inside the completion function. This is the asynchronous code. It runs later — possibly much later, and certainly after the surrounding function doSomeNetworking has returned.

This means that the surrounding function cannot return a value from the asynchronous code. Beginners sometimes try to write this sort thing:

func doSomeNetworking() -> UIImage? { // vain attempt to return an image
    // ... prepare url ...
    var image : UIImage? = nil
    let session = URLSession.shared
    let task = session.downloadTask(with:url) { loc, resp, err in
        if let loc = loc, let d = try? Data(contentsOf:loc) {
            let im = UIImage(data:d)
            image = im // too late!
        }
    }
    task.resume()
    return image // can only be nil!
}

The author of that code evidently hopes that the image will be downloaded and returned from the surrounding function doSomeNetworking. But that can never work, because the last line, return image, will execute before the line image = im has a chance to execute. Thus, the returned UIImage will always be nil.

Beginners might then think: So maybe I can wait until my asynchronous code has finished. That is wrong! Asynchronous means you don’t wait. When you obtain a value in some asynchronous code and you want to do something with that value, do it in the asynchronous code.

Suppose, for example, that our goal is to update the interface with the downloaded image. Then we update the interface in the asynchronous code, after the image has been downloaded:

func doSomeNetworking() {
    // ... prepare url ...
    let session = URLSession.shared
    let task = session.downloadTask(with:url) { loc, resp, err in
        if let loc = loc, let d = try? Data(contentsOf:loc) {
            let im = UIImage(data:d)
            DispatchQueue.main.async {
                self.iv.image = im // update the interface _here_
            }
        }
    }
    task.resume()
}

That’s an excellent solution. But now let’s say you really do want to hand back a value from the asynchronous code to whoever called the surrounding function in the first place, leaving it up to the caller what to do with it. We’ve already established that you can’t return the value. But you can call back to whoever called the surrounding function in order to hand them the value.

A typical architecture is that you allow the caller to hand you a completion function. Inside your asynchronous code, you call the caller’s completion function, like this:

func doSomeNetworking(callBackWithImage: @escaping (UIImage?) -> ()) {
    let s = "https://www.apeth.net/matt/images/phoenixnewest.jpg"
    let url = URL(string:s)!
    let session = URLSession.shared
    let task = session.downloadTask(with:url) { loc, resp, err in
        if let loc = loc, let d = try? Data(contentsOf:loc) {
            let im = UIImage(data:d)
            callBackWithImage(im) // call the caller's completion function
        }
    }
    task.resume()
}

Let’s look at that example from the caller’s point of view. The caller of doSomeNetworking(callBackWithImage:) passes in a completion function that does whatever the caller ultimately wants done. Here, once again, our goal is to update the interface with the downloaded image:

doSomeNetworking { im in
    // this is the completion function!
    DispatchQueue.main.async {
        self.iv.image = im
    }
}

That completion function, too, is asynchronous! The caller doesn’t know when or whether this completion function will be called back; perhaps there aren’t even any rules about what thread it will be called back on. But when and if it is called back, the image will arrive as its parameter — and now the caller can dispose of it as desired. That is the pattern used throughout Cocoa; it propagates asynchronousness. You should understand this pattern, become comfortable with it, and implement it in your own code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.230.82