块和 ViewController 线程安全
我一直在查看游戏中心代码示例, 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];
}];
}
}
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 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];
}];
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题是块有自己的状态。如果您使用变量创建块,则该变量可能会被复制并在块的生命周期内保留。因此,如果您创建一个使用指向视图控制器的指针的块,则该指针可能会被复制,如果该指针引用的内容随后被释放,那么这将是一件坏事。
考虑以下顺序:
您显示的代码通过确保块在其状态中不包含指向视图控制器的指针来避免此问题。它只是调用主线程上的一个方法,并且该方法使用它自己的最新指针来访问视图控制器。如果视图控制器被释放,这个指针应该被设置为 nil,这样就不会发生什么不好的事情。
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:
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.
我对块和 GCD 也很陌生,这就是为什么我在 Google 中找到你的问题。
然而,在阅读了另一次讨论后,我认为也许迦勒的答案并不完全正确?
后台线程上释放 UI 对象
Block_release在 其他讨论,拉尔夫说:
“UIKit 对象不喜欢在主线程之外取消分配”,
在 GKTapper 的评论中:
“视图控制器可能会在主队列之外释放(并释放)”
我认为情况更像是:
不确定这是否正确,但是目前,这就是我的理解。
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:
Not sure if this is correct, but at the moment, this is what I understand.