Я тестирую некоторый код, который выполняет асинхронную обработку с использованием Grand Central Dispatch. Тестовый код выглядит так:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
Тесты должны ждать окончания операции. Мое текущее решение выглядит так:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
Что выглядит немного грубо, знаете ли вы лучший способ? Я мог бы выставить очередь и затем заблокировать, вызвав dispatch_sync
:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
... но это может показаться слишком много на object
.
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
сwhile (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; }
В дополнение к семафорной технике, подробно рассмотренной в других ответах, теперь мы можем использовать XCTest в Xcode 6 для выполнения асинхронных тестов через
XCTestExpectation
. Это устраняет необходимость в семафорах при тестировании асинхронного кода. Например:Ради будущих читателей, хотя техника семафоров рассылки является замечательной техникой, когда она абсолютно необходима, я должен признаться, что вижу слишком много новых разработчиков, незнакомых с хорошими шаблонами асинхронного программирования, слишком быстро тяготеющих к семафорам в качестве общего механизма создания асинхронных процедуры ведут себя синхронно. Хуже того, я видел, что многие из них используют эту технику семафоров из основной очереди (и мы никогда не должны блокировать основную очередь в производственных приложениях).
Я знаю, что это не тот случай (когда этот вопрос был опубликован, не было такого хорошего инструмента, как
XCTestExpectation
; также в этих тестах мы должны убедиться, что тест не завершится, пока не будет выполнен асинхронный вызов). Это одна из тех редких ситуаций, когда может понадобиться техника семафора для блокировки основного потока.Поэтому, приношу свои извинения автору этого оригинального вопроса, для которого техника семафора является разумной, я пишу это предупреждение всем тем новым разработчикам, которые видят эту технику семафора и рассматривают ее применение в своем коде как общий подход к работе с асинхронными методы: имейте в виду, что в девяти случаях из десяти техника семафоров нелучший подход при включении асинхронных операций. Вместо этого ознакомьтесь с шаблонами завершения / закрытия, а также с шаблонами протоколов делегирования и уведомлениями. Часто это гораздо лучшие способы решения асинхронных задач, чем использование семафоров для синхронного поведения. Обычно есть веские причины, по которым асинхронные задачи были разработаны для асинхронного поведения, поэтому используйте правильный асинхронный шаблон, а не пытайтесь заставить их вести себя синхронно.
источник
NSOperationQueue
. Если я не использую что-то вроде семафора, загрузка документовNSOperation
будет сразу же завершена, и не будет никакой реальной очереди загрузок - они будут в значительной степени выполняться одновременно, чего я не хочу. Семафоры здесь разумны? Или есть лучший способ заставить NSOperations ждать асинхронного завершения других? Или что-то другое?AFHTTPRequestOperation
объекты, тогда вам нужно просто создать операцию завершения (которую вы сделаете зависимой от других операций). Или используйте группы рассылки. Кстати, вы говорите, что не хотите, чтобы они работали одновременно, и это хорошо, если это то, что вам нужно, но вы платите серьезное снижение производительности, выполняя это последовательно, а не одновременно. Я обычно используюmaxConcurrentOperationCount
4 или 5.Недавно я снова пришел к этой проблеме и написал следующую категорию
NSObject
:Таким образом, я могу легко превратить асинхронный вызов с обратным вызовом в синхронный в тестах:
источник
Как правило, не используйте ни один из этих ответов, они часто не масштабируются (конечно, есть исключения здесь и там)
Эти подходы несовместимы с тем, как GCD предназначен для работы, и в конечном итоге приводят к возникновению взаимоблокировок и / или уничтожению батареи путем непрерывного опроса.
Другими словами, переставьте ваш код так, чтобы не было синхронного ожидания результата, а вместо этого имейте дело с результатом, уведомляемым об изменении состояния (например, обратные вызовы / протоколы делегирования, доступность, уход, ошибки и т. Д.). (Они могут быть реорганизованы в блоки, если вам не нравится ад обратного вызова.) Потому что это способ показать реальное поведение остальной части приложения, чем скрыть его за ложным фасадом.
Вместо этого используйте NSNotificationCenter , определите собственный протокол делегата с обратными вызовами для вашего класса. И если вам не нравится гадить со всеми ответными вызовами делегатов, оберните их в конкретный прокси-класс, который реализует пользовательский протокол и сохраняет различные блоки в свойствах. Возможно также предоставить удобные конструкторы.
Начальная работа немного больше, но она уменьшит количество ужасных условий гонки и опросов об убийствах батарей в долгосрочной перспективе.
(Не спрашивайте о примере, потому что это тривиально, и нам пришлось потратить время, чтобы изучить основы target-c.)
источник
Вот изящный трюк, который не использует семафор:
Что вы делаете, это ждите, используя
dispatch_sync
пустой блок для синхронного ожидания в очереди последовательной отправки, пока не завершится блок A-Synchronous.источник
Пример использования:
источник
Есть также SenTestingKitAsync, который позволяет вам писать код следующим образом:
(См. Статью objc.io для деталей.) И, начиная с Xcode 6, есть
AsynchronousTesting
категория,XCTest
которая позволяет вам писать код следующим образом:источник
Вот альтернатива одного из моих тестов:
источник
NSCondition
документации на-waitUntilDate:
«Вы должны заблокировать приемник до вызова этого метода». Так-unlock
должно быть после-waitUntilDate:
.Это сделало это для меня.
источник
Иногда циклы Timeout также полезны. Можете ли вы подождать, пока не получите некоторый (может быть BOOL) сигнал от асинхронного метода обратного вызова, но что, если ответа нет, и вы хотите выйти из этого цикла? Здесь ниже решение, в основном ответ выше, но с добавлением Timeout.
источник
Очень примитивное решение проблемы:
источник
Свифт 4:
Используйте
synchronousRemoteObjectProxyWithErrorHandler
вместоremoteObjectProxy
при создании удаленного объекта. Больше не нужно семафор.Приведенный ниже пример вернет версию, полученную от прокси. Без
synchronousRemoteObjectProxyWithErrorHandler
него произойдет сбой (при попытке доступа к недоступной памяти):источник
Мне нужно подождать, пока загрузится UIWebView, прежде чем запускать мой метод, я смог добиться этого, выполнив проверки готовности UIWebView в главном потоке с использованием GCD в сочетании с методами семафоров, упомянутыми в этом потоке. Конечный код выглядит так:
источник