Блокировка и безопасность потоков ViewController - PullRequest
2 голосов
/ 03 апреля 2011

Я смотрел на пример кода 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];
        }];
    }
}

Ответы [ 2 ]

5 голосов
/ 03 апреля 2011

Проблема в том, что блоки имеют свое собственное состояние. Если вы создаете блок с использованием переменной, эта переменная может быть скопирована и сохранена в течение всего срока действия блока. Итак, если вы создаете блок, который использует указатель на контроллер представления, этот указатель может быть скопирован, и это плохо, если вещь, на которую ссылается указатель, впоследствии освобождается.

Рассмотрим следующую последовательность:

  1. Вы создаете блок, который ссылается на ваш контроллер вида, и передаете его в Game Center.
  2. В главном потоке происходят разные вещи, в результате чего контроллер представления освобождается и освобождается.
  3. Game Center выполняет блок, который вы ему дали. У него все еще есть указатель на контроллер представления, но контроллер представления больше не существует.
  4. Краш.

Код, который вы показываете, позволяет избежать этой проблемы, гарантируя, что блок не содержит указатель на контроллер представления в своем состоянии. Он просто вызывает метод в основном потоке, и этот метод использует свой собственный актуальный указатель для доступа к контроллеру представления. Если контроллер представления был освобожден, этот указатель должен был быть установлен в ноль, поэтому ничего плохого не происходит.

1 голос
/ 08 августа 2012

Я довольно плохо знаком с блоками и GCD, поэтому я нашел ваш вопрос в Google.

Однако, прочитав еще одно обсуждение, я думаю, что, возможно, ответ Калеба не совсем корректен? Block_release освобождает объекты пользовательского интерфейса в фоновом потоке

В другом обсуждении Ральф сказал: «Объекты UIKit не любят, когда их выделяют вне основного потока»

И в комментарии от GKTapper: «контроллер представления может быть выпущен (и dealloc'd) вне главной очереди»

Я думаю, что ситуация больше похожа на:

  1. Вы создаликонтроллер представления (сохранить количество = 1)
  2. Вы создали блок, который также сохранил этот контроллер представления (сохранить количество = 2)
  3. Контроллер представления, созданный в (1), освобождается первым (сохранить количество =1)
  4. Блок выполняется во вторичном потоке, затем освобождается (оставьте счет = 0)
  5. Просмотр освобождения контроллера вне основного потока

Не уверенесли это правильно, но на данный момент это то, что я понимаю.

...