Swift code being executed asynchronously even whil

2019-09-02 03:59发布

问题:

I'm rather new at swift and have been doing some research on how to answer this question myself since I want to learn, but I am completely stumped.

I have a function which requests data from a server, and after the data is received, a completion handler is executed which parses the data. Within the previously mentioned completion handler, another function is called which is passed a completion handler itself.

For some reason, the function call within the function is being being skipped, and being finished after the first completion handler is fully executed. This might make more sense with the code below:

func loadSites(forceDownload: Bool){
    self.inspectionSites = MyData.getLocallyStoredInspectionSites()
    if self.inspectionSites.count < 1 || forceDownload {

        self.http.requestSites({(sitesAcquired, jsonObject) -> Void in
            guard sitesAcquired else{
                SwiftOverlays.removeAllBlockingOverlays()
                MyAlertController.alert("Unable to acquire sites from server or locally")
                return
            }
            let result = jsonObject

            for (_,subJson):(String, JSON) in result!.dictionaryValue {
                let site = InspectionSite()
                site.name = subJson[self.currentIndex]["name"].string!
                site.city = subJson[self.currentIndex]["city"].string!
                site.address = subJson[self.currentIndex]["address"].string!
                site.state = subJson[self.currentIndex]["state"].string!
                site.zip = subJson[self.currentIndex]["zip"].stringValue
                site.siteId = subJson[self.currentIndex]["id"].string!

                objc_sync_enter(self) //SAW A STACKOVERFLOW POST WITH THIS, THOUGHT IT MIGHT HELP
                MyLocation.geoCodeSite(site, callback:{(coordinates) -> Void in
                    print("YO!!!! GEOCODING SITE!")
                    self.localLat = coordinates["lat"]!
                    self.localLon = coordinates["lon"]!
                })
                objc_sync_exit(self)

                for type in subJson[self.currentIndex]["inspection_types"]{
                    let newType = InspectionType()
                    newType.name = type.1["name"].string!
                    newType.id = type.1["id"].string!
                    site.inspectionTypes.append(newType)
                }



                site.lat = self.localLat
                print("HEYY!!!! ASSIGNING COORDS")
                site.lon = self.localLon
                let address = "\(site.address), \(site.city), \(site.state) \(site.zip)"
                site.title = site.name
                site.subtitle = address
                MyData.persistInspectionSite(site)
                self.currentIndex++
            }

            self.inspectionSites = MyData.getLocallyStoredInspectionSites()
            SwiftOverlays.removeAllBlockingOverlays()
            self.showSitesOnMap(self.proteanMap)
        })


    }else{
        SwiftOverlays.removeAllBlockingOverlays()
        self.showSitesOnMap(self.proteanMap)
    }


}

I added those print statements which print "YOOO" and "HEYYY" just so I could see what was being executed first, and "HEYY" is always first. I just need to make sure that the geocoding always happens before the object is persisted. I saw a stackoverflow post which mentioned objc_sync_enter(self) for synchronous operation, but im not even sure if it's what I need.

This is the function which geocodes the site (incase it helps):

    class func geoCodeSite(site: InspectionSite, callback: ((coordinates: Dictionary<String, String>)->Void)?) {
    let geocoder = CLGeocoder()

    let address: String = "\(site.address), \(site.city), \(site.state) \(site.zip)"
    print(address)
    geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
        if((error) != nil){
            print("Error", error)
        }
        if let placemark = placemarks?.first {
             MyLocation.mLat = String(stringInterpolationSegment:placemark.location!.coordinate.latitude)
             MyLocation.mLon = String(stringInterpolationSegment:placemark.location!.coordinate.longitude)
             MyLocation.coordinates = ["lat":mLat, "lon":mLon]
            print(MyLocation.coordinates)
             callback?(coordinates: MyLocation.coordinates)
        }
    })
}

回答1:

I think the behaviour your seeing is expected. You have two levels of asynchronous methods:

requestSites
    geoCodeSite

Since the geoCodeSite method is also asynchronous, its callback is executed well after the line:

MyData.persistInspectionSite(site)

So your problem is how to wait till all InspectionSites have geocoded before persisting the site, right?

Dispatch groups can be used to detect when multiple asynchronous events have finished, see my answer here.

How to Implement Dispatch Groups

dispatch_groups are used to fire a callback when multiple async callbacks have finished. In your case, you need to wait for all geoCodeSite async callbacks to complete before persisting your site.

So, create a dispatch group, firing off your geoCodeSite calls, and implement the dispatch callback inside of which you can persist your geocoded sites.

var myGroup = dispatch_group_create()
dispatch_group_enter(myGroup)
...
fire off your geoCodeSite async callbacks
...
dispatch_group_notify(myGroup, dispatch_get_main_queue(), {
    // all sites are now geocoded, we can now persist site
})

Don't forget to add

dispatch_group_leave(myGroup)

inside the closure of geoCodeSite! Otherwise dispatch_group will never know when your async call finish.