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];

Я не прав? То есть есть ли разница между командами PerformSelector* и GCD? Я прочитал много документации по ним, но до сих пор не видел окончательного ответа.


person akaru    schedule 07.03.2011    source источник
comment
withObject:YES не будет работать и должно дать вам как минимум предупреждение. Это может быть одним из преимуществ GDC, где вы можете отправлять произвольные аргументы получателю.   -  person Felix Lamouroux    schedule 08.03.2011
comment
Правильно, мне нужно обернуть это в NSNumber. Но, игнорируя эту часть, есть ли что-то еще, что отличается? Однако хороший момент.   -  person akaru    schedule 08.03.2011


Ответы (3)


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

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

- (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 в цикле выполнения текущего потока в начале следующей итерации цикла выполнения. Таймер сконфигурирован для работы в режимах, указанных в параметре Modes. Когда таймер срабатывает, поток пытается удалить сообщение из очереди цикла выполнения и выполнить селектор. Это удается, если цикл выполнения запущен и находится в одном из указанных режимов; в противном случае таймер ожидает, пока цикл выполнения не перейдет в один из этих режимов.

person Jacob Relkin    schedule 07.03.2011

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

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

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

Вызов этой функции и обращение к текущей очереди приводит к взаимоблокировке.

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

подождите

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

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

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

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

Обновление: в ответ на сообщение Дэйва Дрибина о предостережения ondispatch_get_current_queue(), я заменил [NSThread isMainThread] в приведенном выше коде.

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

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

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

person Brad Larson    schedule 07.03.2011
comment
@Joe - dispatch_sync() в основной поток все еще может вызвать взаимоблокировку в вашем приложении, если что-то в основном потоке было заблокировано в ожидании значения из вашего фонового потока (через dispatch_sync() или что-то еще). Это очень маловероятно, но все же возможно. Это стандартная проблема двух потоков, ожидающих, пока другой что-то сделает, поэтому ничего не делается. - person Brad Larson; 28.03.2011
comment
@Joe - Да, если вы полностью работаете в неосновной очереди или потоке, вам не нужен код, который у меня есть выше. Проблема взаимоблокировки, о которой я только что упоминал, будет проблемой и для этого кода. Это больше архитектурный вопрос. Что касается комментариев, см. этот метавопрос: meta.stackexchange.com/questions/43019/ . - person Brad Larson; 28.03.2011
comment
Переполнение педантичности: вы можете использовать dispatch_block_t в качестве типа аргумента вместо уродливого void (^block)(void). - person Jacob Relkin; 01.08.2011
comment
В разделе предостережений говорится, что вам разрешено сравнивать очереди для проверки личности (что вы делали). Какая оговорка вас беспокоит здесь? - person Adam Ernst; 21.08.2011
comment
@Adam Адам - ​​Похоже, раздел был обновлен с тех пор, как я это написал. Раньше он говорил вам, что проверка личности не гарантирует работу для dispatch_get_current_queue(). - person Brad Larson; 21.08.2011
comment
С тех пор это было добавлено в раздел предостережений: It is equally unsafe for code to assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue(). - person conradev; 05.07.2012
comment
Во многих случаях единственная ситуация, когда что-то выполняется не в основном потоке, связана с асинхронным http-запросом, а код, выполняемый методом делегата в ответ, может все еще находиться в другом потоке. Я использовал этот код для проверки, прежде чем вызывать методы делегата для всех моих запросов Http, и это сэкономило мне день... Спасибо! (: - person Aviel Gross; 01.12.2013
comment
@BradLarson Как насчет того, чтобы просто использовать dispatch_async(dispatch_get_main_queue(), block)? Любая проблема ? - person keywind; 31.07.2014

Способ GCD считается более эффективным и простым в обращении и доступен только в iOS4 и более поздних версиях, тогда как PerformSelector поддерживается в более старой и новой iOS.

person Seyther    schedule 07.03.2011