iosmultithreadingweb-servicesgrand-central-dispatchkvnprogress

Issues related to calling UIKit methods from non-main thread


I implemented login method in this way:

      [KVNProgress show];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //some error handling like:
                if ([_usernameField.text length] < 4) {
                    [KVNProgress showErrorWithStatus:@"Username too short!"];
                    _passwordField.text = @"";
                    return;
                }
       //Then I call login web service synchronously here:
       result = [ServerRequests login];
       dispatch_async(dispatch_get_main_queue(), ^{
       if(!result)
                    {
                        [KVNProgress showErrorWithStatus:@"problem!" completion:NULL];
                        _passwordField.text = @"";
                    }
                    else if([result.successful boolValue])
                    {
                        [KVNProgress showSuccessWithStatus:result.message];

                    }

});
});

It crashed mostly and by surrounding blocks with only Main Queue (no priority default one) that solved! but the problem is:KVNProgress is only showing in error handling area not the next part that we call web service. It's not user friendly at all! Any idea is welcomed :)


Solution

  • You MUST call methods that update the user interface in any way from the main thread, as per the UIKit documentation:

    For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.

    I suggest you try to limit the number of callbacks you make to the main thread, so therefore you want to batch as much user interface updates together as you can.

    Then all you have to do, as you correctly say, is to use a dispatch_async to callback to your main thread whenever you need to update the UI, from within your background processing.

    Because it's asynchronous, it won't interrupt your background processing, and should have a minimal interruption on the main thread itself as updating values on most UIKit components is fairly cheap, they'll just update their value and trigger their setNeedsDisplay so that they'll get re-drawn at the next run loop.


    From your code, it looks like your issue is that you're calling this from the background thread:

    if ([_usernameField.text length] < 4) {
        [KVNProgress showErrorWithStatus:@"Username too short!"];
        _passwordField.text = @"";
        return;
    }
    

    This is 100% UI updating code, and should therefore take place on the main thread.

    Although, I have no idea about the thread safety of KVNProgress, I assume it should also be called on the main thread as it's presenting an error to the user.

    Your code therefore should look something like this (assuming it's taking place on the main thread to begin with):

    [KVNProgress show];
    
    //some error handling like:
    if ([_usernameField.text length] < 4) {
        [KVNProgress showErrorWithStatus:@"Username too short!"];
        _passwordField.text = @"";
        return;
    }
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        //Then I call login web service synchronously here:
        result = [ServerRequests login];
    
        dispatch_async(dispatch_get_main_queue(), ^{
            if(!result) {
                [KVNProgress showErrorWithStatus:@"problem!" completion:NULL];
                _passwordField.text = @"";
            } else if([result.successful boolValue]) {
                [KVNProgress showSuccessWithStatus:result.message];
            }
        });
    });