Appendix C. How Asynchronous Works

A very common beginner mistake is rooted in a failure to understand what it means for code to run asynchronously. It means that your code runs out of order. Consider:

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 — because 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 well after the surrounding function doSomeNetworking has returned.

An important implication is that it is impossible for the surrounding function to return a value based on what happens in the asynchronous code. Beginners often try to write this sort thing:

func doSomeNetworking() -> UIImage? {
    // ... 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
}

That can never work, because the line return image will execute before the line image = im has a chance to execute. Thus, our returned UIImage is useless: it will always be nil.

Beginners sometimes say: So how can I wait until my asynchronous code has finished? That is wrong. Asynchronous means you don’t wait. Instead, when you get values in an asynchronous completion function and you want to do something with them, do it in the completion function:

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()
}

Very well, but let’s say you really do want to hand back the resulting image to whoever called doSomeNetworking in the first place, leaving it up to the caller what to do with it. I’ve already established that you can’t return the image. But you can call back to whoever called doSomeNetworking and hand them the image. A typical arrangement is that the caller will hand you another completion function. Inside your asynchronous completion function, you call the caller’s completion function — like this:

func doSomeNetworking(callBackWithImage: @escaping (UIImage?) -> Void) {
    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)
        }
    }
    task.resume()
}

That is the strategy used throughout Cocoa itself, when it hands you a completion function which you are requred to call. Here, it effectively propagates asynchronousness. The caller of doSomeNetworking passes in a completion function:

doSomeNetworking { im in
    DispatchQueue.main.async {
        self.iv.image = im
    }
}

The caller doesn’t know when or whether or on what thread this completion function will be called back. But if it is called back, the image will arrive and the caller can now dispose of it as desired.

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

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