4

I'm working on a project that calculates the ETA between various coordinates on a MapView. I use the asynchronous method calculateETAWithCompletionHandler to get the ETA between two coordinates and because calculateETAWithCompletionHandler is asynchronous, my code is not linear.

I need my code to be linear in order to display correct ETA information in a TableView, so I've tried to implement a closure to return in the asynchronous call. However, this still hasn't made my code linear. The following is what I have so far,

override func viewDidLoad() {
    var etaBetween1n2 = 0

    let point1 = MKPointAnnotaion()
    point1.coordinate = CLLocationCoordinate2D(latitude: 36.977317, longitude: -122.054255)
    point1.title = "Point 1"
    mapView.addAnnotation(point1)

    let point2 = MKPointAnnotaion()
    point2.coordinate = CLLocationCoordinate2D(latitude: 36.992781, longitude: -122.064729)
    point2.title = "Point 2"
    mapView.addAnnotation(point2)

    print("A")

    // Closure
    calculateETA(point1, destination: point2) { (eta: Int) -> Void in
        print("B")
        etaBetween1n2 = eta
    }

    print("C")

}

// Calculates ETA between source and destination
// Makes calculateETAWithCompletionHandler call which is asynchronous
func calculateETA(source: MKPointAnnotation, destination: MKPointAnnotation, result: (eta: Int) -> Void) {
    var eta = 0

    let request = MKDirectionsRequest()
    let sourceItem = MKMapItem(placemark: MKPlacemark(coordinate: source.coordinate, addressDictionary: nil))
    request.source = sourceItem
    request.transportType = .Automobile

    let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: destination.coordinate, addressDictionary: nil))
    request.destination = destinationItem

    request.destination = destinationItem
    request.requestsAlternateRoutes = false
    let directions = MKDirections(request: request)

    directions.calculateETAWithCompletionHandler { (etaResponse, error) -> Void in
        if let error = error {
            print("Error while requesting ETA : \(error.localizedDescription)")
        } else {
            eta = Int((etaResponse?.expectedTravelTime)!)
            result(eta: eta)
        }
    }

}

I was hoping that the closure would make my code linear by printing,

A
B
C

but instead it still prints,

A
C
B

Am I implementing the closure incorrectly or are closures the wrong approach for this?

Ivan
  • 665
  • 2
  • 10
  • 28
  • Ideally you would be calling a method that refreshes your view from inside the closure, like reloadData, or something similar. – diatrevolo Feb 12 '16 at 19:38

2 Answers2

1

So a closure is a callback meaning it continue to run the code the rest of code after the call to that function is made, and then once it is ready it will run the callback function.

So if you want to do something after it has finished then you would put it in callback so where you put print("B")

If you can calculating something your code should not be linear because it will freeze the main thread and the screen would become unresponsive until the action has finished.

What are you trying to do after?

Nicholas Mata
  • 796
  • 1
  • 9
  • 19
  • I see. What I'm trying to do is calculate the ETA between various points (not just two) so I actually have my closure inside a for loop that iterates over points (I just mentioned two for simplicity here), then sorting the points from shortest ETA to longest. I make a call to a function `sortETAs()` after the closure, but they're always 0 in `sortETA()` and now I know why. – Ivan Feb 12 '16 at 19:51
  • So should make the call to sortETAs inside the end of the callback. I will also provide some code above of how I would write it. – Nicholas Mata Feb 12 '16 at 19:58
  • Thank you for your help! I'm thinking of passing a list of points into `calculateETA` instead of one at a time and returning a list of Ints (for ETA of each point in the list). – Ivan Feb 12 '16 at 20:14
1

If you want "C" to happen after B, then you have to invoke it from within the closure:

print("A")
calculateETA(point1, destination: point2) { (eta: Int) -> Void in
    print("B")
    etaBetween1n2 = eta

    print("C")
}

If "C" is a step more complicated than a simple print statement, which I'm sure it is. Encapsulate it in a function, and call the function from within the closure.

    doStepA()
    calculateETA(point1, destination: point2) { (eta: Int) -> Void in
        doStepB()
        etaBetween1n2 = eta

        doStepC()
    }
}

func doStepA() {
    print("A")
}

func doStepB() {
    print("B")
}

func doStepC() {
    print("C")
}
raf
  • 2,569
  • 20
  • 19