Reputation: 1296
I'm looking for a good idiom or two for this situation:
I want to convert a CLLocationCoordinate2D to a CLPlacemark with an asynchronous reverse geolocation call, as part of a sequence of other operations.
The conversion step is very much a "utility" step, so putting a lot of code in the handler to do the "other operations" feels like poor structure.
I can store the result in a class variable but then I need to know when the async step has completed, which means some kind of event trigger or queueing of the main thread with timeouts or something, which also seem awkward.
Is there a standard approach to this? Is it common to just put the code in the handler?
Thanks!
Here's the specific code for my context, FWIW.
func getPlaceFromCoordinate(coordinate: CLLocationCoordinate2D) -> CLPlacemark? {
var loc = CLLocation(
latitude: coordinate.latitude,
longitude: coordinate.longitude
)
var mightBeAPlace: CLPlacemark? = nil
CLGeocoder().reverseGeocodeLocation(loc, completionHandler: {(placemarks, error) -> Void in
if(error != nil) {
println("Reverse geocoding error.")
}
else if (placemarks.count == 0) {
println("no placemarks")
}
else { // if (placemarks.count > 0)
println("we have placemarks")
mightBeAPlace = CLPlacemark(placemark: placemarks[0] as! CLPlacemark)
println("Inside closure place: \(mightBeAPlace?.locality)")
lastUserSelectedPlace = mightBeAPlace // This stores it in a class variable.
}
})
println("Outside closure place: \(mightBeAPlace?.locality)")
return mightBeAPlace // This of course fails because the async task is running separately.
}
Upvotes: 1
Views: 1687
Reputation: 1296
The approach I decided on is to write the getPlaceFromCoordinate function to accept an optional closure, so the calling method can control what's done with the result of the lookup.
func getPlaceFromCoordinate(
coordinate: CLLocationCoordinate2D,
placeAction: ((CLPlacemark) -> Void)?
) {
:
// Reverse geocode.
:
// If we get a good placemark:
if (placeAction != nil) {
placeAction!(placemark)
}
}
This seems suitably straightforward for the context, flexible and puts the calling code back "in the drivers seat". Not sure what other pros or cons there might be.
Upvotes: 0
Reputation: 437542
The typical approach is to adopt the completionHandler
approach yourself, e.g.:
lazy var geocoder = CLGeocoder()
func getPlaceFromCoordinate(coordinate: CLLocationCoordinate2D, completionHandler: (CLPlacemark!, NSError?) -> ()) {
let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if error != nil {
println("Reverse geocoding error: \(error)")
} else if placemarks.count == 0 {
println("no placemarks")
}
completionHandler(placemarks.first as? CLPlacemark, error)
}
}
And you'd call it like so:
getPlaceFromCoordinate(coordinate) { placemark, error in
if placemark != nil {
// use placemark here
}
}
// but do not use it here, because the above runs asynchronously (i.e. later)
In terms of how much code you put in this completionHandler
closure, and how much you put in getPlaceFromCoordinate
, that's entirely a function of what that code entails. But as much routine code that is repeated (e.g. logging of errors, what have you) inside the getPlaceFromCoordinate
, and hopefully the closure will be limited to taking the CLPlacemark
and updating model objects and/or UI.
But, yes, the convention is to put anything contingent upon the completion of the asynchronous method inside the completion handler. While there are techniques to make this asynchronous method behave synchronously, that's generally a very bad idea.
If you're finding that the code inside the closure is getting unwieldy, then engage in functional decomposition and move this code into its own function and have the completion handler simply call that. Or there are other asynchronous patterns, too (e.g. asynchronous NSOperation
subclasses with dependencies between them, promises/futures, etc.). But use an asynchronous pattern.
Upvotes: 6