1

My question is very similar to several others here but I just can't get it to work. I'm making an API call via a helper class that I wrote.

First I tried a standard function with a return value and the result was as expected. The background task completed after I tired to assign the result.

Now I'm using a closure and I can get the value back into my view controller but its still stuck in the closure, I have the same problem. I know I need to use GCD to get the assignment to happen in the main queue.

this is what I have in my view controller

var artists = [String]()
let api = APIController()
    api.getArtistList("foo fighters") { (thelist) -> Void in
        if let names = thelist {
            dispatch_async(dispatch_get_main_queue()) {
                artists = names
                print("in the closure: \(artists)")
            }
        }
    }

    print ("method 1 results: \(artists)")

as the results are:

method 1 results: []
in the closure: [Foo Fighters & Brian May, UK Foo Fighters, John Fogerty with Foo Fighters, Foo Fighters, Foo Fighters feat. Norah Jones, Foo Fighters feat. Brian May, Foo Fighters vs. Beastie Boys]

I know why this is happening, I just don't know how to fix it :( The API calls need to be async, so what is the best practice for capturing these results? Based on what the user selects in the table view I'll be making subsequent api calls so its not like I can handle everything inside the closure

Community
  • 1
  • 1
rjb101
  • 514
  • 5
  • 14

2 Answers2

4

I completely agree with the @Craig proposal of the use of the GCD, but as your question involves the request of the API call every time you select a row, you can do the following:

  • Let's suppose you use the tableView:didSelectRowAtIndexPath: method to handle the selection, then you can do the following inside it:

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
       // it is just a form to get the item 
       let selectedItem = items.objectAtIndex(indexPath.row) as String
    
       api.getArtistList(selectedItem) { (thelist) -> Void in
             if let names = thelist {
                dispatch_async(dispatch_get_main_queue()) {
                   artists = names
                }
             }
       } 
    }
    

And then you can observe the property and handle do you want inside it :

var artists: [String] = [] {
   didSet {
      self.tableView.reloadData() // or anything you need to handle.
   }
}

It just another way to see it. I hope this help you.

Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
1

The easy solution is to do whatever you're doing at your print(), inside the closure.

Since you're already dispatch_asyncing to the main queue (the main/GUI thread), you can complete any processing there. Push a new view controller, present some modal data, update your current view controller, etc.

Just make sure that you don't have multiple threads modifying/accessing your local/cached data that is being displayed. Especially if it's being used by UITableViewDelegate / UITableViewDataSource implementations, which will throw fits if you start getting wishy-washy or inconsistent with your return values.

As long as you can retrieve the data in the background, and the only processing that needs to occur on the main thread is an instance variable reassignment, or some kind of array appending, just do that on the main thread, using the data you retrieved on the back end. It's not heavy. If it is heavy, then you're going to need more sophisticated synchronization methods to protect your data.

Normally the pattern looks like:

dispatch_async(getBackgroundQueue(), {
    var theData = getTheDataFromNetwork();
    dispatch_async(dispatch_get_main_queue() {
        self.data = theData // Update the instance variable of your ViewController
        self.tableView.reloadData() // Or some other 'reload' method
    });
})

So where you'd normally refresh a table view or notify your ViewController that the operation has completed (or that local data has been updated), you should continue your main-thread processing.

Craig Otis
  • 31,257
  • 32
  • 136
  • 234
  • This works perfectly thanks. The view initially loads with an empty table and then populates. – rjb101 Jul 07 '15 at 21:30
  • No problem Robert. I would strongly recommend investing a few minutes into https://github.com/jdg/MBProgressHUD. I'm not the author, but it's my go-to framework for dropping in a quick loading overlay while waiting for data to show up. If a user doesn't have network connectivity, they might wonder - _is it still thinking_? – Craig Otis Jul 07 '15 at 21:32