简体   繁体   中英

Swift: get value using async method

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.
}

The typical approach is to adopt the completionHandler approach yourself, eg:

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 (eg 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 (eg asynchronous NSOperation subclasses with dependencies between them, promises/futures, etc.). But use an asynchronous pattern.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM