块和ViewController线程安全

时间:2011-04-03 06:12:14

标签: iphone objective-c-blocks game-center gamekit grand-central-dispatch

我一直在关注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];
        }];
    }
}

2 个答案:

答案 0 :(得分:5)

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

考虑以下顺序:

  1. 您创建一个引用视图控制器并将其提供给Game Center的块。
  2. 主线程上发生了各种各样的事情,导致视图控制器被释放和解除分配。
  3. Game Center会执行您提供的阻止。它仍然有一个指向视图控制器的指针,但视图控制器不再存在。
  4. 崩溃。
  5. 您显示的代码通过确保块在其状态中不包含指向视图控制器的指针来避免此问题。它只是在主线程上调用一个方法,该方法使用自己的最新指针来访问视图控制器。如果取消分配视图控制器,则此指针应设置为nil,因此不会发生任何不良情况。

答案 1 :(得分:1)

我对块和GCD也很陌生,这就是我在谷歌找到你的问题的原因。

然而,在阅读了另一个讨论之后,我认为Caleb的回答可能并不完全正确? Block_release deallocating UI objects on a background thread

在另一场讨论中,拉尔夫说: “UIKit对象不喜欢在主线程之外取消分配”

在GKTapper的评论中: “视图控制器可能会在主队列”

之外被释放(和解除分配)

我认为情况更像是:

  1. 您创建了视图控制器(保留计数= 1)
  2. 您创建了一个也保留此视图控制器的块(保留计数= 2)
  3. 首先释放在(1)中创建的视图控制器(保留计数= 1)
  4. 阻止在辅助线程上执行,然后释放(保留计数= 0)
  5. 查看控制器在主线程外被解除分类
  6. 不确定这是否正确,但目前,这是我理解的。