28

I am using a NSURLSession to get the values to populate a TableView. I am updating the TableView in the completion handler, but using [[NSThread currentThread] isMainThread] has shown me that the completion handler isn't running in the main thread. Since I should only updating the UI from the main thread, I know this isn't correct. Is there a way to trigger an action on the main thread from the completion handler? Or is using a NSURLSession the wrong way to go about this?

NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:@"http://myurl"]
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            NSError *jsonError = nil;
            NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
            if (jsonError) {
                NSLog(@"error is %@", [jsonError localizedDescription]);
                // Handle Error and return
                return;
            }
            self.userArray = jsonUsers;
            [self.userTableView reloadData];
            if ([[NSThread currentThread] isMainThread]){
                NSLog(@"In main thread--completion handler");
            }
            else{
                NSLog(@"Not in main thread--completion handler");
            }
}] resume];
otter
  • 383
  • 1
  • 3
  • 6

6 Answers6

51

Yes, just dispatch your main thread stuff using GCD:

 NSURLSession *session = [NSURLSession sharedSession];
    [[session dataTaskWithURL:[NSURL URLWithString:@"http://myurl"]
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                NSError *jsonError = nil;
                NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
                if (jsonError) {
                    NSLog(@"error is %@", [jsonError localizedDescription]);
                    // Handle Error and return
                    return;
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    self.userArray = jsonUsers;
                    [self.userTableView reloadData];
                    if ([[NSThread currentThread] isMainThread]){
                        NSLog(@"In main thread--completion handler");
                    }
                    else{
                        NSLog(@"Not in main thread--completion handler");
                    }
                });

            }] resume];
graver
  • 15,183
  • 4
  • 46
  • 62
  • Thanks! By GDC you're talking about Grand Central Dispatch, right? And in general terms, would I want to do as much computation outside the new block? For example, if I was doing a bunch of stuff instead of just `self.userArray = jsonUsers;`, perhaps I'd put all that before the `dispatch_asyn(...`? – otter Nov 18 '13 at 17:07
  • Yes, sorry GCD. And Yes, doing all the hard work before the the block would be done in a background thread. – graver Nov 18 '13 at 17:09
  • @graver at this line: `NSURLSession *session = [NSURLSession sharedSession];` which thread/queue that this session runs on ? I thin k this will be main queue. but base on this question/answer I think I guess wrong. Can you tell me this point, please ? thanks :) – hqt Aug 15 '14 at 19:48
  • @hqt that'll be a concurrent thread for the sharedSession either. – valeCocoa Mar 31 '17 at 04:26
22

@graver's answer is good. Here's another way you can do it:

NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                          delegate:nil
                                                     delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithURL:[NSURL URLWithString:@"http://myurl"]
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            NSError *jsonError = nil;
            NSArray* jsonUsers = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
            if (jsonError) {
                NSLog(@"error is %@", [jsonError localizedDescription]);
                // Handle Error and return
                return;
            }

            self.userArray = jsonUsers;
            [self.userTableView reloadData];
            if ([[NSThread currentThread] isMainThread]){
                NSLog(@"In main thread--completion handler");
            }
            else{
                NSLog(@"Not in main thread--completion handler");
            }
        }] resume];

This way you create a session that calls the completion block and any delegate methods on the main thread. You may find this more aesthetically pleasing, but you do lose the advantage of running the "hard work" in the background.

bugloaf
  • 2,890
  • 3
  • 30
  • 49
4

Here is the best way to update UI from blocks and completion handler, and also when you not confrim which thread running your code.

static void runOnMainThread(void (^block)(void))
{
    if (!block) return;

    if ( [[NSThread currentThread] isMainThread] ) {
        block();
    } else {
        dispatch_async(dispatch_get_main_queue(), block);
    }
}

This is static method which will have a block, and will run on main thread, it will act like a

runOnMainThread(^{
           // do things here, it will run on main thread, like updating UI

        });
Fattie
  • 27,874
  • 70
  • 431
  • 719
Adnan Aftab
  • 14,377
  • 4
  • 45
  • 54
  • See now, actually its a static method which accept a block and will run that block of code on main thread... You can use this code in your application as Utility method. – Adnan Aftab Nov 18 '13 at 16:33
  • HI @C_X I thick you **HAD A TYPO**. It's [[NSThread currentThread] isMainThread], not [NSThread isMainThread]. I edited it. Feel free to edit again as you wish! – Fattie Sep 29 '14 at 08:06
3

You can try this:

[self.userTableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
Big-O Claire
  • 956
  • 8
  • 13
0

send a notification on completion that will be observed by the table view controller which will then do a reloadData;

this has the advantage that if you later move this download code off to a separate class, e.g. a model object, then no changes are required and also the code can be reused in other projects without making changes

SPA
  • 1,279
  • 8
  • 13
  • Unless you take precautions, however, the notification will be observed on the same thread in which it is posted. – bugloaf Mar 04 '15 at 19:33
0

Swift 3.1

DispatchQueue.main.async {
    tableView.reloadData()
}
William Hu
  • 15,423
  • 11
  • 100
  • 121