Alex Smith
Alex Smith

Reputation: 85

Calling asynchronous tasks one after the other in Swift (iOS)

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

Answers (2)

Alex Smith
Alex Smith

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

kas-kad
kas-kad

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

Related Questions