На ум приходят три способа: NSRunLoop, семафоры и группы.
NSRunLoop
__block bool finished = false;
// For testing purposes we create this asynchronous task
// that starts after 3 seconds and takes 1 second to execute.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_time_t threeSeconds = dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC);
dispatch_after(threeSeconds, queue, ^{
sleep(1); // replace this with your task
finished = true;
});
// loop until the flag is set from inside the task
while (!finished) {
// spend 1 second processing events on each loop
NSDate *oneSecond = [NSDate dateWithTimeIntervalSinceNow:1];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:oneSecond];
}
NSRunLoop - это цикл, который обрабатывает такие события, как сетевые порты, клавиатура или любой другой источник входного сигнала, который вы подключаете, и возвращает после обработки этих событий или после ограничения по времени. Когда нет событий для обработки, цикл выполнения переводит поток в спящий режим. Все приложения Cocoa и Core Foundation имеют цикл выполнения. Подробнее о циклах выполнения вы можете прочитать в Руководстве по программированию потоков Apple: Циклы выполнения или в Mike Ash Пятница, вопросы и ответы 2010-01-01: NSRunLoop Internals .
В этом тесте я просто использую NSRunLoop, чтобы приостановить поток на секунду. Без этого постоянный цикл в while
потреблял бы 100% ядра ЦП.
Если блок и логический флаг созданы в одной и той же лексической области (например, оба в методе), тогда для флага необходимо, чтобы спецификатор хранения __block
был изменяемым. Если бы флаг был глобальной переменной, он бы не понадобился.
Если тест завершается неудачей перед установкой флага, поток застревает в ожидании навсегда. Добавьте ограничение по времени, чтобы избежать этого:
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:2];
while (!finished && [timeout timeIntervalSinceNow]>0) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
if (!finished) NSLog(@"test failed with timeout");
Если вы используете этот код для модульного тестирования, альтернативный способ вставить тайм-аут - отправить блок с утверждением:
// taken from https://github.com/JaviSoto/JSBarrierOperationQueue/blob/master/JSBarrierOperationQueueTests/JSBarrierOperationQueueTests.m#L118
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2LL * NSEC_PER_SEC);
dispatch_after(timeout, dispatch_get_main_queue(), ^(void){
STAssertTrue(done, @"Should have finished by now");
});
Семафор
Аналогичная идея, но спит до тех пор, пока семафор не изменится, или до ограничения времени:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// signal the semaphore after 3 seconds using a global queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3LL*NSEC_PER_SEC), queue, ^{
sleep(1);
dispatch_semaphore_signal(semaphore);
});
// wait with a time limit of 5 seconds
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5LL*NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout)==0) {
NSLog(@"success, semaphore signaled in time");
} else {
NSLog(@"failure, semaphore didn't signal in time");
}
dispatch_release(semaphore);
Если бы вместо этого мы ждали вечно с dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
, мы застряли бы до получения сигнала от задачи, которая продолжает работать в фоновой очереди.
* Группа 1040 *
Теперь представьте, что вам нужно подождать несколько блоков. Вы можете использовать int как флаг, или создать семафор, который начинается с большего числа, или вы можете группировать блоки и ждать, пока группа не будет завершена. В этом примере я делаю позже только с одним блоком:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
// dispatch work to the given group and queue
dispatch_group_async(group,queue,^{
sleep(1); // replace this with your task
});
// wait two seconds for the group to finish
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2LL*NSEC_PER_SEC);
if (dispatch_group_wait(group, timeout)==0) {
NSLog(@"success, dispatch group completed in time");
} else {
NSLog(@"failure, dispatch group did not complete in time");
}
dispatch_release(group);
Если по какой-то причине (для очистки ресурсов?) Вы хотите запустить блок после завершения группы, используйте dispatch_group_notify(group,queue, ^{/*...*/});