2

I wanted to update the UI in the NSURLSession's completion block. The initial implementation didn't update the UI immediately. It updated the UI maybe 20 seconds later. Here is the initial implementation.

  NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 200, 150, 150)];
        label.backgroundColor = [UIColor greenColor];
        label.text = jsonDict[@"object_or_array"];

        dispatch_async(dispatch_get_main_queue(), ^{ 
            [self.view addSubview:label];
        });
    }];

    [task resume];

I moved the location of the label.text = jsonDict[@"object_or_array"] inside the main queue as following.

 dispatch_async(dispatch_get_main_queue(), ^{ 
                label.text = jsonDict[@"object_or_array"]
                [self.view addSubview:label];
            });

Then the UI was updated immediately as expected.

Could anyone tell me why this is?

6
  • 4
    All UI updates must be done on the main thread. Commented Jan 27, 2015 at 19:02
  • Creating the label, setting the backgroundColor, and setting the text should all be done on the main thread. You should never touch UI objects on a background thread, because the UI objects and the whole UI implementation on iOS is not thread-safe. Commented Jan 27, 2015 at 19:06
  • 2
    @user3386109 That's not quite true. It's safe to do some things such as dynamically create UIImage objects in a background thread. From the UIView docs: "Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread." Commented Jan 27, 2015 at 19:11
  • We use async threads to do long running tasks in background so that UI wouldn't block as our application is running on main thread,in your case while doing updation within completion handler the UI was updated when the control arrived at that very line in which you were trying to update code, and on the other hand when you used main queue your long running task pushed to background thread and UI updated immediately after control arrives on that line. Commented Jan 27, 2015 at 19:12
  • @rmaddy I agree in theory and disagree in practice. The statement "The only time this may not be strictly necessary..." is about as unreassuring as documentation can get. So unless there's some overwhelming benefit to creating the UIView on the background thread (highly unlikely), I stand by my comment that the UIView should be created on the main thread. Commented Jan 27, 2015 at 19:23

1 Answer 1

3

As @rmaddy has said:

Threading Considerations

Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.

UIView Class Reference

From this quote, I infer the following rules:

  • Instances of classes that inherit from UIView should only have methods called on the main thread (including property getters and setters).
  • Initialization methods (-init*) of UIKit objects can be done in any thread.
  • Any method can be called on instances of UIKit classes not sub-classed from UIView from any thread.

Here is the code I would feel comfortable with:

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 200, 150, 150)]; // OK because -initWithFrame: is initialization
UIColor *color = [UIColor greenColor]; // OK because UIColor is not a subclass of UIView
NSString *text = jsonDict[@"object_or_array"];

dispatch_async(dispatch_get_main_queue(), ^{ 
    label.backgroundColor = color;
    label.text = text;
    [self.view addSubview:label];
});

But really, the safest is to do it all from the main thread:

dispatch_async(dispatch_get_main_queue(), ^{ 
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 200, 150, 150)];
    label.backgroundColor = [UIColor greenColor];
    label.text = jsonDict[@"object_or_array"];
    [self.view addSubview:label];
});

Unless you are running into performance issues, there is no reason not to.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.