Я смотрел на пример кода Game Center, GKTapper , и один раздел, где разработчик комментирует его реализацию, не имеет для меня особого смысла. Код вставлен ниже. Я не понимаю, почему отправка блока, который модифицирует viewcontroller в главном потоке, не является безопасной?
Он упоминает, что «Если на viewcontroller ссылаются в блоке, который выполняется во вторичной очереди, то он может быть освобожден за пределами основной очереди. Это верно, даже если фактический блок запланирован в основном потоке». Как это возможно, если код, связанный с выпуском, находится в основном потоке пользовательского интерфейса (в главном цикле выполнения)? Или есть что-то с Blocks / GCD, которое я не получаю?
Что еще более любопытно для меня, так это то, как его решение решает эту проблему. «Поскольку« callDelegate »является единственным методом доступа к делегату, я могу гарантировать, что делегат не будет виден ни в одном из моих обратных вызовов блока». (Делегат - это 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];
}];
}
}