开发者

Blocks and ViewController Thread Safety

I've been looking at the Game Center code example, GKTapper, and one section where the developer comments on his implementation does not make too much sense to me. The code is inserted below. What I do not understand is why would dispatching a block that modifies a viewcontroller on the main thread not be safe?

He mentions that "If the viewcontroller is referenced in a block that executes on a secondary queue, then it may be released outside the main queue. This is true even if the actual block is scheduled on the main thread." How is that possible if the code dealing with the release is on the main UI thread (on the main runloop)? Or is there something with Blocks/GCD that I am not getting?

What's even more curious to me is how his solution even solves this problem. "Because "callDelegate" is the only method to access the delegate, I can ensure that delegate is not visible in any of my block callbacks." (Delegate is a viewcontroller here)

Could someone enlighten me about this entire thing? I'm quite new to blocks and GCD so perhaps I am missing something simple...

// NOTE:  GameCenter does not guarantee that callback blocks will be execute on the main thread.
// As such, your application needs to be very careful in how it handles references to view
// controllers.  If a view controller is referenced in a block that executes on a secondary queue,
// that view controller may be released (and dealloc'd) outside the main queue.  This is true
// even if the actual block is scheduled on the main thread.  In concrete terms, this code
// snippet is not safe, even though viewController is dispatching to the main queue:
//
//  [object doSomethingWithCallback:  ^()
//  {
//      dispatch_async(dispatch_get_main_queue(), ^(void)
//      {
//          [viewController doSomething];
//      });
//  }];
//
// UIKit view controllers should only be accessed on the main thread, so the snippet above may
// lead to subtle and hard to trace bugs.  Many solutions to this problem exist.  In this sample,
// I'm bottlenecking everything through  "callDelegateOnMainThread" which calls "callDelegate".
// Because "callDelegate" is the only method to access the delegate, I can ensure that delegate
// is not visible in any of my block callbacks.
// *** Delegate in this case is a view controller. ***
- (void) callDelegate: (SEL) selector wit开发者_开发知识库hArg: (id) arg error: (NSError*) err
{
    assert([NSThread isMainThread]);
    if([delegate respondsToSelector: selector])
    {
        if(arg != NULL)
        {
            [delegate performSelector: selector withObject: arg withObject: err];
        }
        else
        {
            [delegate performSelector: selector withObject: err];
        }
    }
    else
    {
        NSLog(@"Missed Method");
    }
}

- (void) callDelegateOnMainThread: (SEL) selector withArg: (id) arg error: (NSError*) err
{
    dispatch_async(dispatch_get_main_queue(), ^(void)
    {
       [self callDelegate: selector withArg: arg error: err];
    });
}

- (void) authenticateLocalUser
{
    if([GKLocalPlayer localPlayer].authenticated == NO)
    {
        [[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error)
        {
            [self callDelegateOnMainThread: @selector(processGameCenterAuth:) withArg: NULL error: error];
        }];
    }
}


The problem is that blocks have their own state. If you create a block using a variable, that variable may be copied and persist for the life of the block. So, if you create a block that uses a pointer to a view controller, that pointer may be copied, and that's a bad thing if the thing that the pointer refers to is subsequently deallocated.

Consider the following sequence:

  1. You create a block that refers to your view controller and give it to Game Center.
  2. Various stuff happens on the main thread, causing the view controller to be released and deallocated.
  3. Game Center executes the block that you gave it. It still has a pointer to the view controller, but the view controller no longer exists.
  4. Crash.

The code you show avoids this problem by ensuring that the block doesn't include a pointer to the view controller in its state. It simply calls a method on the main thread, and that method uses its own up-to-date pointer to access the view controller. If the view controller was deallocated, this pointer should have been set to nil, so nothing bad happens.


I am quite new to blocks and GCD too, which is why I found your question in Google.

However, after reading another discussion, I think that maybe Caleb's answer is not entirely correct? Block_release deallocating UI objects on a background thread

In the other discussion, Ralph said: "UIKit objects do not like to be de-allocated outside of the main thread"

And in the comment from GKTapper: "view controller may be released (and dealloc'd) outside the main queue"

I think the situation is more like:

  1. You created view controller (retain count=1)
  2. You created a block that also retained this view controller (retain count=2)
  3. View controller created in (1) is released first (retain count=1)
  4. Block being executed on a secondary thread, then released (retain count=0)
  5. View controller being dealloc'd outside main thread

Not sure if this is correct, but at the moment, this is what I understand.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜