Search code examples
swiftnsurlsessioncompletion-block

NSURLSession, Completion Block, Swift


Im working with NSURLSession. I have an array with restaurants and i'm requesting the dishes for every restaurant in the array to the api. The dataTask works,i'm just having a real hard time trying to call a method only when the all dataTasks are finished.

 self.findAllDishesOfRestaurants(self.restaurantsNearMe) { (result) -> Void in
        if result.count != 0 {
              self.updateDataSourceAndReloadTableView(result, term: "protein")
        } else {
            print("not ready yet")
        } 
    }

the self.updateDataSourceAndREloadTableView never gets called, regardless of my completion block. Here is my findAllDishesOfRestaurants function

func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
    let allDishesArray:NSMutableArray = NSMutableArray()
    for restaurant in restaurants as! [Resturant] {
        let currentRestaurant:Resturant? = restaurant
        if currentRestaurant == nil {
            print("restaurant is nil")
        } else {
            self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
                                            if let dishesArray:NSArray = result {
                                                restaurant.dishes =  dishesArray
                                                print(restaurant.dishes?.count)
                                                allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
                                                self.allDishes.addObjectsFromArray(dishesArray as [AnyObject])
                                                print(self.allDishes.count)
                                            }
                                            else {
                                                print("not dishes found")
                                        }
                                          // completion(result:allDishesArray)
                                    })
             completion(result:allDishesArray)
        }
    }
}

And here is my the function where i perform the dataTasks.

 func getDishesByRestaurantName(restaurant:Resturant, completion:(result:NSArray) ->Void) {

    var restaurantNameFormatted = String()
    if let name = restaurant.name {
    for charachter in name.characters {
        var newString = String()
        var sameCharacter:Character!
        if charachter == " " {
           newString = "%20"
            restaurantNameFormatted = restaurantNameFormatted + newString
        } else {
            sameCharacter = charachter
            restaurantNameFormatted.append(sameCharacter)
        }
       // print(restaurantNameFormatted)
    }
}
    var urlString:String!
        //not to myself, when using string with format, we need to igone all  the % marks arent ours to replace with a string, otherwise they will be expecting to be replaced by a value
         urlString = String(format:"https://api.nutritionix.com/v1_1/search/%@?results=0%%3A20&cal_min=0&cal_max=50000&fields=*&appId=XXXXXXXXXappKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXX",restaurantNameFormatted)
    let URL = NSURL(string:urlString)
    let restaurantDishesArray = NSMutableArray()
  let session = NSURLSession.sharedSession()
                let dataTask = session.dataTaskWithURL(URL!) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
                do {
                let anyObjectFromResponse:AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
                    if let asNSDictionary = anyObjectFromResponse as? NSDictionary {
                        let hitsArray = asNSDictionary.valueForKey("hits") as? [AnyObject]
                                                for newDictionary in hitsArray! as! [NSDictionary]{
                                                    let fieldsDictionary = newDictionary.valueForKey("fields") as? NSDictionary
                                                    let newDish = Dish.init(dictionary:fieldsDictionary!, restaurant: restaurant)
                                                    restaurantDishesArray.addObject(newDish)
                        }
                    }
                    completion(result:restaurantDishesArray)
                } catch let error as NSError {
                    print("failed to connec to api")
                    print(error.localizedDescription)
                }
            }
            dataTask.resume()
}

Like i said before, I need to wait until the fun findAllDishesOfRestaurants is done. I tried writing my completion blocks but I'm not sure I'm doing it right. Any help is greatly appreciated. Thank


Solution

  • The problem is that you are calling the completion method in findAllDishesOfRestaurants before al tasks are complete. In fact, you are calling it once for each restaurant in the list, which is probably not what you want.

    My recommendation would be for you to look into NSOperationQueue for two reasons:

    1. It will let you limit the number of concurrent requests to the server, so your server does not get flooded with requests.
    2. It will let you easily control when all operations are complete.

    However, if you are looking for a quick fix, what you need is to use GCD groups dispatch_group_create, dispatch_group_enter, dispatch_group_leave, and dispatch_group_notify as follows.

    func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
        let group = dispatch_group_create() // Create GCD group
    
        let allDishesArray:NSMutableArray = NSMutableArray()
        for restaurant in restaurants as! [Resturant] {
            let currentRestaurant:Resturant? = restaurant
            if currentRestaurant == nil {
                print("restaurant is nil")
            } else {
                dispatch_group_enter(group) // Enter group for this restaurant
                self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
                    if let dishesArray:NSArray = result {
                        restaurant.dishes =  dishesArray
                        print(restaurant.dishes?.count)
                        allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
                        // self.allDishes.addObjectsFromArray(dishesArray as [AnyObject])  <-- do not do this
                        // print(self.allDishes.count)
                    }
                    else {
                        print("not dishes found")
                    }
                    // completion(result:allDishesArray)  <-- No need for this, remove
                    dispatch_group_leave(group) // Leave group, marking this restaurant as complete
                })
                // completion(result:allDishesArray) <-- Do not call here either
            }
        }
    
        // Wait for all groups to complete
        dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
            completion(result:allDishesArray)
        }
    }