1

The User is on a map view. When doing a long press somewhere on the map the following function gets triggered to set a new annotation inclusively a proper title.

func action(gestureRecognizer: UIGestureRecognizer) {

    if gestureRecognizer.state == UIGestureRecognizerState.Began {

        var newTouch: CGPoint = gestureRecognizer.locationInView(self.mapView)

        var newCoordinate: CLLocationCoordinate2D = mapView.convertPoint(newTouch, toCoordinateFromView: self.mapView)

        var newLocation = CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude)

        var newAnnotation = MKPointAnnotation()

        newAnnotation.coordinate = newCoordinate

        CLGeocoder().reverseGeocodeLocation(newLocation, completionHandler: {(placemarks, error) in

            if error != nil { println(error) }

            let p: CLPlacemark = placemarks[0] as CLPlacemark

            var thoroughfare: String? = p.thoroughfare

            var subThoroughfare: String? = p.subThoroughfare

            if p.thoroughfare == nil || p.subThoroughfare == nil {

                var date = NSDate()

                newAnnotation.title = "Added \(date)"

            } else {

                newAnnotation.title = thoroughfare! + " " + subThoroughfare!

            }

        })

        self.mapView.addAnnotation(newAnnotation)

        self.mapView.selectAnnotation(newAnnotation, animated: true)

        places.append(["name":"\(newAnnotation.title)", "lat":"\(newCoordinate.latitude)", "lon":"\(newCoordinate.longitude)"])

    }

}

I know it is working fine when keeping the last three lines of code within the CLGeocoder block (closure?). But if I separate those and list them after the }) (or put some of the code to another thread) I'm facing the problem that its running asynchronous (as I don't understand how to control async vs sync) and by the time the annotation is added to the map and saved to places its title is not set yet by the CLGeocoder. A beginner to programming is asking: What would be necessary to be implemented (disptach_sync...-something) so the last lines of code wait for the CLGeocoder block to finish? I haven't managed to implement this command in the right way yet...

alexeis
  • 2,152
  • 4
  • 23
  • 30
  • 2
    Look up dispatch groups. – Abizern Sep 02 '14 at 23:58
  • There is not enough information here. Assuming `namingAnnotation` is synchronous there is no further action necessary. – jtbandes Sep 03 '14 at 00:37
  • I edited the question to point out my actual problem. I know it's a widely used (and well known) topic but as I'm new to programming I sometimes just don't get the guides I've been reading right yet. – alexeis Sep 03 '14 at 10:36
  • @Abizern You can use dispatch groups (or semaphores) to make an asynchronous function behave like a synchronous one, but only if the asynchronous task (and its completion closure) runs in a separate thread. In the case of `reverseGeocodeLocation`, the completion closure always runs on the main thread, so you will deadlock if you use the dispatch group/semaphore technique from the main thread. (You'd never want to do it from the main thread, anyway.) In this case, you'd have to dispatch all of this to some background thread if you want to use the dispatch group/semaphore trick. – Rob Sep 03 '14 at 18:28

1 Answers1

5

You correctly point out that that it works when you put those three lines within the geocoder's closure. So, you should do precisely that: Leverage this asynchronous pattern and put everything dependent upon the geocode process inside the closure.

In answer to your question, yes, there are patterns which can make an asynchronous task behave in a synchronous manner. For example, you can use dispatch groups or dispatch semaphores. But you are calling this from an IBAction, which runs on the main thread. And you never want to block the main thread. So any attempt to make this geocode request run synchronously from the main thread is ill-advised.

Furthermore, the above process is complicated by a wrinkle of reverseGeocodeLocation: Specifically, the closure, itself, runs on the main thread, so if you block the main thread waiting for the closure to finish, the app will deadlock. So the above patterns won't even work with reverseGeocodeLocation (if done from the main thread).

Bottom line, you should embrace the asynchronous patterns, keeping dependent code inside the closure.


As an aside, your example was a simple one, where you'd just put the code you want to perform inside the geocoder's closure. But what if you wanted to have two separate functions, for example, one for geocoding which returned an annotation, and another function for adding the annotation to the map. Perhaps you were anticipating something like:

func handleLongPress(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        let location = gesture.locationInView(self.mapView)
        let annotation = geocodeLocationInMapView(self.mapView, location: location)
        addAnnotationToMapView(self.mapView, annotation: annotation)
    }
}

And then the question would be how would you have the second function, addAnnotationToMapView, wait for the first, geocodeLocationInMapView, to complete. Again, the answer is "you don't." Instead, just like Apple does with their asynchronous methods, you would employ a completion block pattern:

func handleLongPress(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        let location = gesture.locationInView(self.mapView)

        geocodeLocationInMapView(self.mapView, location: location) { annotation, error in
            guard annotation != nil && error == nil else {
                print("error = \(error)")
                return
            }

            self.addAnnotationToMapview(self.mapView, annotation: annotation!)
        }
    }
}

In that case, the geocodeLocationInMapView might look like:

func geocodeLocationInMapView(mapView: MKMapView, location: CGPoint, completion: (MKAnnotation?, NSError?) -> ()) {
    let coordinate = mapView.convertPoint(location, toCoordinateFromView: mapView)
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

    CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
        guard placemarks != nil && error == nil else {
            completion(nil, error)
            return
        }

        if let placemark = placemarks!.first {
            let annotation = MKPointAnnotation()
            annotation.coordinate = coordinate
            let thoroughfare = placemark.thoroughfare
            let subThoroughfare = placemark.subThoroughfare

            switch (thoroughfare, subThoroughfare) {
            case (nil, nil):
                annotation.title = "Added \(NSDate())"
            case (_, nil):
                annotation.title = thoroughfare
            default:
                annotation.title = thoroughfare! + " " + subThoroughfare!
            }

            completion(annotation, nil)
        }
    }
}

And the addAnnotationToMapview might look like:

func addAnnotationToMapview(mapView: MKMapView, annotation: MKAnnotation) {
    mapView.addAnnotation(annotation)
    mapView.selectAnnotation(annotation, animated: true)

    places.append(["name":"\(annotation.title)", "lat":"\(annotation.coordinate.latitude)", "lon":"\(annotation.coordinate.longitude)"])
}

This is, admittedly, a contrived example, and I'd suggest you handle your IBAction like I described at the start of this answer. But in answer to the broader question of "how do I have function A wait until function B is finished", you might employ a completion block pattern as outlined here.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Wow, thanks for this generous and well-explained answer! It hit exactly my actual question. Even though I'm still lacking a bit understanding all the parts of this "completion block" concept I think it brought me a huge step forward and will allow me to work myself through! Thanks! (I'd love to upvote your answer but I'm not even allowed to yet with my ridiculous 3 reputation points) – alexeis Sep 04 '14 at 10:26
  • My pleasure. In this case, just put those three lines in the completion block of the geocoder and you can declare success. But you'll eventually want to familiarize yourself with implementing your own completion block patterns as it's invaluable when doing asynchronous programming and you'll find yourself using it a lot. It's tempting to want to make asynchronous methods run synchronously using GCD, but it's better to jump into proper asynchronous patterns, IMHO. – Rob Sep 04 '14 at 13:34
  • Right! But while I've got most of the GCD concept by now, understanding asynchronous vs synchronous execution and the different kind of queues with their behaviors my brain just keeps miss something to understand "completion handlers". Maybe I'm already using such but if, subconsciously, because by now I couldn't give any convinced answer to a question like "What is a completion handler in the sense of what is its purpose, why is it used and in what context". For about the first time not even google wants to tell me and in every guide explaining something, this is assumed to be pre-known... – alexeis Sep 04 '14 at 15:14
  • Let me take a crack: A completion handler is a block of code used in conjunction with some asynchronous function (i.e. a function that starts some slow operation, but returns immediately to let current thread carry on; but the completion block will be called when the slow asynchronous task is done). The iOS API uses this pattern all over the place, so you're already familiar with it (such as the `reverseGeocodeLocation`). What's not immediately obvious is how we implement our own completion handlers in our own code, which is what I tried to illustrate in the second part of my answer. – Rob Sep 04 '14 at 15:31
  • Rob, thanks for your patience! I hope I'm on the right way, finally... So a completion handler is nothing else than a named closure that handles the function's returning value allowing to bypass the common "(parameter)->return"-function format like "geocodeLocationInMapView(self.mapView, location: location) -> MKPointAnnotation" that would block the rest of the thread while running in that case. So actually allowing the wrapping function to have no return that had to be waited for but still getting a value later on through the completion handler closure that itself has a return. More or less? – alexeis Sep 04 '14 at 15:55
  • More or less. It's not really the presence or absence of the completion handler in `geocodeLocationInMapView` function definition that dictates this behavior, but rather the specifics of the `geocodeLocationInMapView` implementation, itself (i.e. use Apple's asynchronous API; don't use dispatch groups/semaphores to make it synchronous; etc.). It's just that if you do proper asynchronous implementation in that function, then if you want to specify some block of code to run when the asynchronous task it initiated is done, then the completion handler is one very common pattern that achieves that. – Rob Sep 04 '14 at 16:30