Ответ 1
Три способа, которые приходят на ум, - это: 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];
}
A NSRunLoop - это цикл, который обрабатывает события, такие как сетевые порты, клавиатуру или любой другой источник входного сигнала, который вы подключаете, и возвращает после обработки этих событий или по истечении срока. Когда нет событий для обработки, цикл запуска помещает поток в режим сна. Все приложения Cocoa и Core Foundation имеют цикл запуска под ним. Вы можете узнать больше о циклах запуска в Руководстве по программированию Apple Threading: Run Loops или в Mike Ash Friday Q & A 2010- 01-01: Внутренние NSRunLoop.
В этом тесте я просто использую 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);
, мы бы застряли, пока не получим сигнал от задачи, которая продолжает работать в фоновом режиме.
Группа
Теперь представьте, что вам нужно подождать несколько блоков. Вы можете использовать флаг 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, ^{/*...*/});