1

I want to write a function to reverse geocode a location and assign the resulting string into a variable. Following this post i've got something like this:

extension CLLocation {

    func reverseGeocodeLocation(completion: (answer: String?) -> Void) {

        CLGeocoder().reverseGeocodeLocation(self) {

            if let error = $1 {
                print("[ERROR] \(error.localizedDescription)")
                return
            }

            if let a = $0?.last {
                guard let streetName = a.thoroughfare,
                    let postal = a.postalCode,
                    let city = a.locality else { return }

                completion(answer: "[\(streetName), \(postal) \(city)]")
            }

        }
    }
}

For calling this function i've just got something like this:

location.reverseGeocodeLocation { answer in
    print(answer)
}

But instead i want to assign the string value of answer to a variable and i don't know how to pass that data out of the closure. What is the best way to do something like this?

Community
  • 1
  • 1
Dominik Sturm
  • 55
  • 1
  • 8

2 Answers2

3

The problem is that it runs asynchronously, so you can't return the value. If you want to update some property or variable, the right place to do that is in the closure you provide to the method, for example:

var geocodeString: String?

location.reverseGeocodeLocation { answer in
    geocodeString = answer
    // and trigger whatever UI or model update you want here
}

// but not here

The entire purpose of the closure completion handler pattern is that is the preferred way to provide the data that was retrieved asynchronously.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
1

Short answer: You can't. That's not how async programming works. The function reverseGeocodeLocation returns immediately, before the answer is available. At some point in the future the geocode result becomes available, and when that happens the code in your closure gets called. THAT is when you do something with your answer. You could write the closure to install the answer in a label, update a table view, or some other behavior. (I don't remember if the geocoding methods' closures get called on the main thread or a background thread. If they get called on a background thread then you would need to wrap your UI calls in dispatch_async(dispatch_get_main_queue()).)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • FWIW, unlike many asynchronous API methods, with `reverseGeocodeLocation` you don't have to worry about dispatching anything to the main queue. As [the docs say](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLGeocoder_class/index.html#//apple_ref/occ/instm/CLGeocoder/reverseGeocodeLocation:completionHandler:), "Your completion handler block will be executed on the main thread." But your point about the main queue is an excellent one, as this is a concern with many other asynchronous methods with completion handlers. – Rob Aug 08 '16 at 00:27
  • 1
    @Rob, Thanks for clarifying that. I did not remember how `reverseGeocodeLocation` handled it's closure and didn't have time to look it up when I posted my answer. – Duncan C Aug 08 '16 at 00:42