By now you may have read this post explaining what “asynchronous” means. If you haven’t, please read it first!
So, if you understand what “asynchronous” means, then you won’t make the biggest mistake that people tend to make with asynchronous code, namely: outside asynchronous code, trying to use a value that depends on asynchronous code.
Here’s an actual example someone posted on Stack Overflow. First, the programmer has some instance properties in his class:
var placemarkLongitude = CLLocationDegrees()
var placemarkLatitude = CLLocationDegrees()
Then, in a method, he does some geocoding, like this:
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
if let placemark = placemarks?[0] {
let placemark = placemarks![0]
self.placemarkLatitude = (placemark.location?.coordinate.latitude)!
self.placemarkLongitude = (placemark.location?.coordinate.longitude)!
}
}))
let eventLatitude = self.placemarkLatitude // 0.0!
let eventLongitude = self.placemarkLongitude // 0.0!
The programmer here is surprised because he can see perfectly well that he sets self.placemarkLatitude
and self.placemarkLongitude
— but when he tries to use those values in the last two lines, they are zero. It’s as if they never got set in the first place. What happened?
If you understand what “asynchronous” means, you understand what’s gone wrong here. The programmer has not grasped that the code runs in a different order from the way it is written. The completionHandler
code in geocoding is asynchronous. Therefore, the last two lines run before the asynchronous code! Like this:
// 1
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
// 4! much later than 3
if let placemark = placemarks?[0] {
// 5
let placemark = placemarks![0]
// 6
self.placemarkLatitude = (placemark.location?.coordinate.latitude)!
// 7
self.placemarkLongitude = (placemark.location?.coordinate.longitude)!
}
}))
// 2
let eventLatitude = self.placemarkLatitude // 0.0!
// 3
let eventLongitude = self.placemarkLongitude // 0.0!
First (2) he fetches self.placemarkLatitude
and (3) self.placemarkLongitude
and sets his local variables eventLatitude
and eventLongitude
. Then, some time later — possibly quite a long time later — (4) the geocoding completes, the completion handler is called, and (6) he sets self.placemarkLatitude
and (7) self.placemarkLongitude
— too late to use them!
This programmer is trying to use a value before it has been set! That is impossible, unless you happen to have a time machine in your pocket. That’s a typical trap you can fall into, if you don’t understand what “asynchronous” means.
Do you see how to solve the problem? The code that is meant to run after the geocoding finishes — and after self.placemarkLatitude
and self.placemarkLongitude
has been set by the asynchronous code — needs to be moved into the asynchronous code, like this:
// 1
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
// 2, some time later
if let placemark = placemarks?[0] {
// 3
let placemark = placemarks![0]
// 4
self.placemarkLatitude = (placemark.location?.coordinate.latitude)!
// 5
self.placemarkLongitude = (placemark.location?.coordinate.longitude)!
// 6
let eventLatitude = self.placemarkLatitude
// 7
let eventLongitude = self.placemarkLongitude
// and so on...
}
}))
One more thing. Often, when people ask about this sort of situation on Stack Overflow, I see them use phrases like “How do I wait for the asynchronous code to finish?” This programmer, asking about the problem, says he wants to wait for self.placemarkLatitude
and self.placemarkLongitude
to be set before using them. But you see from the example that that’s the wrong way to look at things. You do not “wait”. Instead, you rearrange your code so that things happen in the right order, no matter when they happen.
Cool! But there’s a variant on this problem that’s even trickier: what we actually want to return a value from the asynchronous code? That’s what we’ll talk about next.