Serial Queue not executing serially

I don't understand why the code is not executing serially. I have called the closures syncronously so that the next closure will be called only after the completion of previous one. On terminal, the last closure is printed first which I was expecting to execute in the end, then the second one & then the first one at last. Any help on this ?

func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(address) { (placemarks, error) in
        guard let placemarks = placemarks,
        let location = placemarks.first?.location?.coordinate
        else {
            completion(nil)
            return
         }
        completion(location)
    }
}

let queue = DispatchQueue(label: "queue")
queue.sync {
    self.getLocation(from: self.sourceString) { location in
        if(location != nil){
            self.source = MKPlacemark(coordinate: location!)
            print("Source found")
         }
         else{
            print("Source not found")
         }
    }
}
queue.sync {
    self.getLocation(from: self.sourceString) { location in
        if(location != nil){
            self.destination = MKPlacemark(coordinate: location!)
            print("Destination found")
         }
         else{
            print("Destination not found")
         }
    }
}
queue.sync {
    if(self.source.coordinate.latitude != 0.0 && self.destination.coordinate.latitude != 0.0 ){
         self.bringMap = true
     }
     else{
         print("coordinates not updated yet")
     }
}

Could you show the sequence of print you get ?

The code is not executing serially because it involves completion handlers, which are inherently asynchronous . Your getLocation function only starts an asynchronous operation, but does not wait for it to finish.

Your queue.sync calls here don't achieve anything here, because the calls to getLocation are already synchronous (only the completion handlers are executed asynchronously).

So, you've effectively written:

    self.getLocation(from: self.sourceString) { … }
    self.getLocation(from: self.destinationString) { … } // I think you meant "destination", not "source" here!
    if(/*some test*/) { /* some stuff */ }

These things run in order, but the parts represented by { … } are completion handlers that run later — which is to say, after your if statement.

If you want to get the destination location only after you have the source location, and test the coordinates after you have both locations, you'd need something like this:

    self.getLocation(from: self.sourceString) { 
        … 
        self.getLocation(from: self.destinationString) { 
            … 
            if(/*some test*/) { /* some stuff */ }
        }
    }

This is pretty ugly, but it's the way we've always done it.

For a better approach, you can use Swift async/await. This is a bigger topic than can be addressed here, but if you look at the CoreLocation documentation, you'll see that the CoreLocation function has an async variant:

https://developer.apple.com/documentation/corelocation/clgeocoder/1423509-geocodeaddressstring/

You can use that to make an async version of your own getLocation function, which means your code will end up looking something like this:

    self.source = try await self.getLocation(from: self.sourceString)
    self.destination = try await self.getLocation(from self.destinationString)
    if (…

I recommend you watch some of the 2022 WWDC videos on Swift concurrency, if you haven't done that already!

Serial Queue not executing serially
 
 
Q