我一直在查看 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];
}];
}
}