2

我一直在查看 Game Center 代码示例GKTapper以及开发人员对其实现的评论对我来说没有太大意义的部分。代码插入下面。我不明白为什么在主线程上调度一个修改视图控制器的块不安全?

他提到“如果在辅助队列上执行的块中引用了视图控制器,那么它可能会在主队列之外释放。即使实际块被调度在主线程上也是如此。” 如果处理发布的代码在主 UI 线程上(在主运行循环上),这怎么可能?还是我没有得到 Blocks/GCD 的东西?

更让我好奇的是他的解决方案是如何解决这个问题的。“因为‘callDelegate’是访问委托的唯一方法,所以我可以确保委托在我的任何块回调中都不可见。” (这里的委托是一个视图控制器)

有人能告诉我这整件事吗?我对块和 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];
        }];
    }
}
4

2 回答 2

5

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

考虑以下顺序:

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

您显示的代码通过确保块在其状态中不包含指向视图控制器的指针来避免此问题。它只是在主线程上调用一个方法,该方法使用它自己的最新指针来访问视图控制器。如果视图控制器被释放,这个指针应该被设置为 nil,所以不会发生任何不好的事情。

于 2011-04-03T06:37:57.583 回答
1

我对块和 GCD 也很陌生,这就是我在 Google 中找到您的问题的原因。

但是,在阅读了另一个讨论之后,我认为也许 Caleb 的答案并不完全正确? Block_release 在后台线程上释放 UI 对象

在另一个讨论中,Ralph 说:“UIKit 对象不喜欢在主线程之外被释放”

在 GKTapper 的评论中:“视图控制器可能会在主队列之外被释放(并释放)”

我认为情况更像是:

  1. 您创建了视图控制器(保留计数 = 1)
  2. 您创建了一个还保留此视图控制器的块(保留计数 = 2)
  3. (1)中创建的视图控制器先释放(retain count=1)
  4. 在辅助线程上执行的块,然后释放(保留计数 = 0)
  5. 视图控制器在主线程外被释放

不确定这是否正确,但目前,这是我所理解的。

于 2012-08-08T01:51:56.520 回答