Обратный вызов NSInvocationOperation слишком скоро
Я знаю, что подобные вопросы задавались несколько раз, но я изо всех сил пытаюсь понять, как эта конкретная проблема может быть решена. Пока что все, что я сделал, было выполнено на главном шаге. Теперь я обнаружил, что мне нужно выполнить операцию, которая займет некоторое время, и я хочу добавить HUD к моему дисплею на время операции и исчезнуть, когда операция будет завершена.
Прочитав много о GCD (и немного запутавшись), я решил, что самым простым способом будет вызвать мой трудоемкий метод с NSInvocationOperation и добавить его во вновь созданный NSOperationQueue. Вот что у меня есть:
[self showLoadingConfirmation]; // puts HUD on screen
// this bit takes a while to draw a large number of dots on a MKMapView
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(timeConsumingOperation:)
object:[self lotsOfDataFromManagedObject]];
// this fades the HUD away and removes it from the superview
[operation setCompletionBlock:^{ [self performSelectorOnMainThread:@selector(fadeConfirmation:) withObject:loadingView waitUntilDone:YES]; }];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
Я ожидал бы, что это покажет HUD, начнет рисовать точки на карте, а затем, когда эта операция будет завершена, исчезнет HUD.
Вместо этого он показывает HUD, начинает рисовать точки на карте и постепенно исчезает, в то же время рисуя точки. Согласно моим NSLogs, примерно четверть секунды задерживается перед вызовом метода для исчезновения HUD. Тем временем рисование точек продолжается еще несколько секунд.
Что я могу сделать, чтобы заставить его ждать, пока рисунок на карте не будет завершен, прежде чем исчезнуть HUD?
Спасибо
ИЗМЕНЕНО ДЛЯ ДОБАВЛЕНИЯ:
Я почти добился успеха после внесения следующих изменений:
NSInvocationOperation *showHud = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(showLoadingConfirmation)
object:nil];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(timeConsumingOperation:)
object:[self lotsOfDataFromManagedObject]];
NSInvocationOperation *hideHud = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(fadeConfirmation:)
object:loadingView];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSArray *operations = [NSArray arrayWithObjects:showHud, operation, hideHud, nil];
[operationQueue addOperations:operations waitUntilFinished:YES];
Странно, но кажется, что сначала вызывается timeConsumingOperation, затем showLoadingConfirmation, затем fadeConfirmation. Это согласно моим NSLogs, которые запускаются в рамках этих методов.
Поведение, которое я вижу на экране, таково: точки нарисованы, и карта корректирует свой масштаб соответственно (часть timeConsumingOperation), затем на экране появляется HUD, затем ничего. Все три NSLogs появляются мгновенно, даже если showLoadingConfirmation не происходит до тех пор, пока не завершится timeConsumingOperation и, по-видимому, fadeConfirmation вообще не происходит.
Это кажется очень странным, но также предполагает, что есть способ заставить что-то произойти по завершении timeConsumingOperation.
Я попытался добавить это:
[operationQueue setMaxConcurrentOperationCount:1];
а также это:
[showHud setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[hideHud setQueuePriority:NSOperationQueuePriorityVeryLow];
но они, кажется, не имеют никакого значения.
2 ответа
Помните о том, что вы на самом деле делаете, когда устанавливаете обработчик завершения для NSInvocationOperation: Когда такая операция завершается, происходит следующее (из ссылки на класс NSOperation):
Точный контекст выполнения для вашего блока завершения не гарантируется, но обычно является вторичным потоком. Следовательно, вы не должны использовать этот блок для выполнения какой-либо работы, которая требует очень определенного контекста выполнения. Вместо этого вы должны шунтировать эту работу в основной поток вашего приложения или в конкретный поток, который способен это сделать. Например, если у вас есть собственный поток для координации завершения операции, вы можете использовать блок завершения для проверки связи с этим потоком.
Итак, во-первых, блок выполняется во вторичном потоке (так что он войдет в очередь этого потока, а когда, наконец, наступит его очередь, он просто отправит другое задание в очередь основного потока). Это означает, что он будет смешиваться в такой очереди с другими ожидающими заданиями, такими как последние обновления для выводов, которые были отправлены в основную очередь из вашего timeConsumingOperation:
селектор.
И здесь возникает проблема: если вы не установите приоритеты для разных заданий, невозможно определить порядок, в котором такие задания будут окончательно обработаны, даже если вы знаете, что они были отправлены раньше времени. Кроме того, в вашем случае тот факт, что ваша NSInvocationOperation завершена , не обязательно означает, что все ваши объекты были нарисованы на экране к моменту вызова блока, это только означает, что они были отправлены в поток обновления пользовательского интерфейса для обработки, когда их очередь наступает.
Имея это в виду и учитывая, что вы не хотите идти по пути GCD, (который я бы порекомендовал сделать еще одну попытку, поскольку я знаю, что это не легко в начале, но когда вы берете его на вооружение, вы понимаете, что это прекрасное решение почти для всех многопоточных приложений, которые вы хотели бы сделать на iPhone), я бы создал NSOperationQueue и отправил бы туда все задания (те, которые удаляют включенный HUD, но с более низким приоритетом, чем другие). Таким образом, вы гарантируете, что удаление HUD обрабатывается в главной очереди после того, как все задания 'pin' выполнены, что было вашей первой целью.
Если HUD исчезает, то вызывается ваш блок завершения, что означает, что ваш timeConsumingOperation:
метод вернулся.
Если вы все еще видите, что точки рисуются, это означает, что транзакции анимации или рисования все еще выполняются или все еще стоят в очереди, даже после timeConsumingOperation:
возвращается. Решение зависит от того, какую технику вы используете для рисования. Вы используете базовую анимацию? MapKit с аннотациями?