问题
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 InspectionSite
s 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.
来源:https://stackoverflow.com/questions/36168541/swift-code-being-executed-asynchronously-even-while-in-completion-handler