Grand Central Dispatch (GCD) против performSelector - требуется лучшее объяснение

Я использовал как GCD, так и performSelectorOnMainThread: waitUntilDone в своих приложениях и, как правило, считаю их взаимозаменяемыми - то есть performSelectorOnMainThread: waitUntilDone - обертка Obj-C для синтаксиса GCD C. Я думал об этих двух командах как эквивалент:

dispatch_sync(dispatch_get_main_queue(), ^{ [self doit:YES]; });


[self performSelectorOnMainThread:@selector(doit:) withObject:YES waitUntilDone:YES];

Я неверен? То есть, существует ли разница в исполнении команд 'Selectlect *' и GCD? Я прочитал много документации по ним, но еще не нашел окончательного ответа.

Ответ 1

performSelectorOnMainThread: использует не GCD для отправки сообщений объектам в основном потоке.

Вот как documentation говорит, что метод реализован:

- (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait {
  [[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes];
}

И на performSelector:target:withObject:order:modes: в документации указано:

Этот метод устанавливает таймер для выполнения сообщения aSelector в текущем цикле выполнения потоков в начале следующей итерации цикла запуска. Таймер сконфигурирован для работы в режимах, указанных параметром режимов. Когда таймер срабатывает, поток пытается удалить сообщение из цикла запуска и выполнить селектор. Это успешно, если цикл запуска запущен и в одном из указанных режимов; в противном случае таймер ждет, пока цикл выполнения не окажется в одном из этих режимов.

Ответ 2

Как указывает Джейкоб, хотя они могут казаться одинаковыми, они разные. На самом деле существует значительная разница в том, как они обрабатывают отправку действий в основной поток, если вы уже используете основной поток.

Я столкнулся с этим недавно, когда у меня был общий метод, который иногда запускался из чего-то в основном потоке, иногда нет. Чтобы защитить определенные обновления пользовательского интерфейса, я использовал -performSelectorOnMainThread: для них без проблем.

Когда я переключился на использование dispatch_sync в основной очереди, приложение заработало бы в тупике всякий раз, когда этот метод запускался в основной очереди. Читая документацию на dispatch_sync, мы видим:

Вызов этой функции и таргетинга текущая очередь приводит к тупиковой ситуации.

где для -performSelectorOnMainThread: мы видим

ожидание

Логическое значение, которое указывает, текущие блоки потока до тех пор, пока указанный селектор выполняется на приемник на основной нити. Указывать ДА, чтобы заблокировать эту тему; в противном случае, укажите NO, чтобы этот метод возвращался немедленно.

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

Я по-прежнему предпочитаю элегантность GCD, лучшую проверку времени компиляции и большую гибкость в отношении аргументов и т.д., поэтому я сделал эту небольшую вспомогательную функцию для предотвращения взаимоблокировок:

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

Обновление: В ответ на Дэйв Дрибин, указав раздел оговорки в dispatch_get_current_queue(), я изменил к использованию [NSThread isMainThread] в приведенном выше коде.

Затем я использую

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

выполнить действия, которые необходимо защитить на основном потоке, не беспокоясь о том, в какой поток был выполнен исходный метод.

Ответ 3

GCD-способ, по-видимому, более эффективен и прост в обращении и доступен только в iOS4, тогда как performSelector поддерживается в более старой и новой iOS.