`
dadi520
  • 浏览: 139186 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

Updating the UI from another thread in iOS

    博客分类:
  • ios
 
阅读更多

http://chritto.wordpress.com/2012/12/20/updating-the-ui-from-another-thread/

Apple’s UIKit is generally designed to be dealt with from within the main thread – operating with it from another thread can result in unpredictable results, including:

  • Everything may be fine
  • The user interface element may not be redrawn after your property changes
  • UIKit will give you a warning in the console, and throw an exception
  • The application will crash
  • The application won’t crash, and the UI will be left in an undefined state

The question then becomes: how can we update our user interface from a background thread? Utilising Apple’s Grand Central Dispatch APIs is one such way.

Suppose one of your UIViewControllers receives a message to update the background color of its main view. This method is called from a background thread (say, when a particular network packet arrives). We can attach ourselves to the main thread, make the call, and then return to our background thread so we can continue to process other network packets.

 
- (void)updateBackgroundColor:(UIColor *)color
{
    dispatch_sync(dispatch_get_main_queue(), ^{
        self.view.backgroundColor = color;
    });
}

Taking a closer look at this, we have:

 
dispatch_sync(dispatch_queue_t queue, ^(void)block)

The “sync” part of the function name (as differentiated from “async”) means that the code that comes after the dispatch_sync call (and in this case, the code which sent the -updateBackgroundColor: message) will not continue to run until the work inside the block is done. This can be useful where the code that follows makes the assumption that the work inside the block has been done (e.g. if it were then to query the backgroundColor property of the view later on). Making the “async” version of the call throws the work to be done on a queue, and processes it later on, when the specified queue gets its chance to run again. It looks similar to the synchronous version:

1
dispatch_async(dispatch_queue_t queue, ^(void)block)

Many of the dispatch_* functions operate on dispatch queues. Here, we are operating on the main queue, which is connected with the main run loop (our UI thread):

 
dispatch_get_main_queue()

What about if we want to be able to call the same method, but from the UI thread? This creates a problem! The dispatch_sync function places its block of work to be done at the end of the main dispatch queue. It will then block until that work has been done. However, we’re already in the main thread, and until we return to the run loop, the block won’t be scheduled to run. This creates a deadlock – something we want to avoid. We can fix this with the following modification:

 
- (void)updateBackgroundColor:(UIColor *)color
{
    if ([NSThread isMainThread])
    {
        self.view.backgroundColor = color;
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), ^{
            self.view.backgroundColor = color;
        });
    }
}

This is a little ugly, and duplicates the code – we can change it like this:

 
- (void)updateBackgroundColor:(UIColor *)color
{
    dispatch_block_t work_to_do = ^{
        self.view.backgroundColor = color;
    }
    if ([NSThread isMainThread])
    {
        work_to_do();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), work_to_do);
    }
}

Even better, we can throw this into a function which can be used elsewhere:

 
void RUN_ON_UI_THREAD(dispatch_block_t block)
{
    if ([NSThread isMainThread])
        block();
    else
        dispatch_sync(dispatch_get_main_queue(), block);
}

so that our code is simplified to:

 
- (void)updateBackgroundColor:(UIColor *)color
{
    RUN_ON_UI_THREAD(^{
        self.view.backgroundColor = color;
    });
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics