块和ViewController线程安全

Sun*_*ist 2 iphone gamekit grand-central-dispatch objective-c-blocks game-center

我一直在关注Game Center代码示例,GKTapper,以及开发人员对其实现的评论对我来说没有多大意义的一个部分.代码插入下面.我不明白为什么调度一个修改主线程上的viewcontroller的块不安全?

他提到"如果在一个在二级队列上执行的块中引用了viewcontroller,那么它可能会在主队列之外被释放.即使在主线程上调度了实际块,也是如此." 如果处理发布的代码在主UI线程上(在主runloop上),那怎么可能呢?或者我有没有得到Block/GCD的东西?

更让我感到好奇的是他的解决方案是如何解决这个问题的."因为"callDelegate"是访问委托的唯一方法,我可以确保委托在任何块回调中都不可见." (Delegate是一个viewcontroller在这里)

有人可以告诉我这件事吗?我对块和GCD很新,所以也许我错过了一些简单的东西......

// 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 withArg: (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];
        }];
    }
}
Run Code Online (Sandbox Code Playgroud)

Cal*_*leb 5

问题是块有自己的状态.如果使用变量创建块,则可以复制该变量并在块的生命周期内保留该变量.因此,如果您创建一个使用指向视图控制器的指针的块,则可能会复制该指针,如果指针引用的内容随后被释放,那将是一件坏事.

请考虑以下顺序:

  1. 您创建一个块,引用您的视图控制器并将其提供给Game Center.
  2. 主线程上发生了各种各样的事情,导致视图控制器被释放和释放.
  3. 游戏中心执行你给它的块.它仍然有一个指向视图控制器的指针,但视图控制器不再存在.
  4. 崩溃.

您显示的代码通过确保块在其状态中不包含指向视图控制器的指针来避免此问题.它只是在主线程上调用一个方法,该方法使用自己的最新指针来访问视图控制器.如果取消分配视图控制器,则此指针应设置为nil,因此不会发生任何不良情况.