Reputation: 85
I'm having problems with trying to run several asynchronous tasks one by one using dispatch async in swift, for an iOS weather app. I want my 'update()' function to:
(Go easy on me, I'm a recently self-taught coder, so I'm assuming the more experienced of you out there will be able to point out various errors! Any help would be massively appreciated.)
var latitude: String = ""
var longitude: String = ""
var locationManager: CLLocationManager!
var forecastData: Weather = Weather() // the weather class has it's own asynchronous NSURLSession called retrieveForecast()
// which calls the Open Weather Map API and parses the XML
func refresh() {
// Here's where I don't know what I'm doing:
let group = dispatch_group_create()
dispatch_group_enter(group)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
self.getLocation()
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
self.getWeather()
}
self.updateUI() // ...and I know that this is in totally the wrong place!
}
// function(s) to get phone's location:
func getLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.distanceFilter = 100.0
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.locationManager.stopUpdatingLocation()
manager.stopUpdatingLocation()
for item: AnyObject in locations {
if let location = item as? CLLocation {
if location.horizontalAccuracy < 1000 {
manager.stopUpdatingLocation()
self.latitude = String(location.coordinate.latitude)
self.longitude = String(location.coordinate.longitude)
}
}
}
}
// function to download and parse the weather data within forecastData object
func getWeather() {
let apiCall = "http://api.openweathermap.org/data/2.5/forecast?lat=" + self.latitude
+ "&lon=" + self.longitude + "&mode=xml&appid=" + self.openWeatherMapAPIKey
NSLog("getWeather called with api request: \(apiCall)")
self.forecastData.retrieveForecast(apiCall)
}
Upvotes: 3
Views: 7441
Reputation: 85
After going off to learn about closures/blocks, I finally found my answer so thought I'd share. What I needed to do was add a closure to the arguments in my Weather class, which returns a boolean value when the XML parsing is complete, and allows me to wait for that to happen in my View Controller before updating the UI. I hope this helps anyone else looking for a similar answer, and if any pros are able to make this code even better, please do add a comment!
...
// getWeather function in my View Controller (called from location manager)
func getWeather() {
let apiCall = "http://api.openweathermap.org/data/2.5/forecast?lat=" + self.latitude
+ "&lon=" + self.longitude + "&mode=xml&appid=" + self.openWeatherMapAPIKey
NSLog("getWeather called with api request: \(apiCall)")
self.forecastData.retrieveForecast(apiCall, completion: { success in
if success {
self.updateUI()
} else {
NSLog("Parse unsuccessful")
// I'll handle the issue here
}
});
}
...And in the Weather class function:
func retrieveForecast(url: String, completion: ((Bool)->())?) {
self.reset()
let apiCall = NSURL(string: url)
let urlRequest: NSURLRequest = NSURLRequest(URL: apiCall!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
self.xmlParser = NSXMLParser(data: data!)
self.xmlParser.delegate = self
let success: Bool = self.xmlParser.parse()
if !success {
NSLog("Did not parse. \(error?.localizedDescription)")
completion?(false)
} else {
self.currentParsedElement = ""
self.retrievingForecast = false
completion?(true)
}
}
task.resume()
}
Upvotes: 2
Reputation: 3764
For any asynchronous operation it is a good manner to have a finish callback.
In your case, if you have implemented callbacks for getLocation
and getWeather
you'll never need dispatch_groups
, you just call getWeather
from getLocation
's callback, and then you just call refreshUI
from getWeather
's callback.
var latitude: String = ""
var longitude: String = ""
var locationManager: CLLocationManager!
var forecastData: Weather = Weather() // the weather class has it's own asynchronous NSURLSession called retrieveForecast()
// which calls the Open Weather Map API and parses the XML
func refresh() {
self.getLocation()
}
// function(s) to get phone's location:
func getLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.distanceFilter = 100.0
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.locationManager.stopUpdatingLocation()
manager.stopUpdatingLocation()
for item: AnyObject in locations {
if let location = item as? CLLocation {
if location.horizontalAccuracy < 1000 {
manager.stopUpdatingLocation()
self.latitude = String(location.coordinate.latitude)
self.longitude = String(location.coordinate.longitude)
self.getWeather()
}
}
}
}
// function to download and parse the weather data within forecastData object
func getWeather() {
let apiCall = "http://api.openweathermap.org/data/2.5/forecast?lat=" + self.latitude
+ "&lon=" + self.longitude + "&mode=xml&appid=" + self.openWeatherMapAPIKey
NSLog("getWeather called with api request: \(apiCall)")
self.forecastData.retrieveForecast(apiCall)
// assuming that self.forecastData.retrieveForecast(apiCall) is completely synchronous, we can call updateUI below
self.updateUI()
}
Here is the code, demonstrating how dispatch_group
can be used right way:
func refetchOrdersAndChats(remoteNotificationData: [NSObject : AnyObject], completion: ((Bool)->())?) {
var overallSuccess: Bool = true
let refetchGroup = dispatch_group_create();
dispatch_group_enter(refetchGroup);
CPChatController.sharedInstance.updateChat(remoteNotificationData, completion: { success in
overallSuccess = success && overallSuccess
dispatch_group_leave(refetchGroup);
})
dispatch_group_enter(refetchGroup);
CPChatController.sharedInstance.fetchNewOrdersWithNotification(remoteNotificationData, completion: { success in
overallSuccess = success && overallSuccess
dispatch_group_leave(refetchGroup);
})
dispatch_group_notify(refetchGroup,dispatch_get_main_queue(), {
completion?(overallSuccess)
})
}
Upvotes: 4