NSOperation completeBlock вызывается дважды

Я работаю над подклассом NSOperation и столкнулся с этой очень странной проблемой, когда блок завершения вызывается дважды подряд. Вызовы KVO выглядят нормально, но завершение блока по-прежнему странно вызывается дважды. Я неправильно понимаю NSOperation? В документации сказано, что блок завершения вызывается, когда isFinished становится YES, и в моем коде это происходит только один раз:

- (void)main {
    @autoreleasepool {
        [self willChangeValueForKey:@"isExecuting"];
        [self willChangeValueForKey:@"isReady"];
        executing = YES;
        [self didChangeValueForKey:@"isReady"];
        [self didChangeValueForKey:@"isExecuting"];

        //start the operation
    }
}

Затем я просто установил completionBlock следующим образом:

self.completionBlock = ^{
    NSLog(@"Completed");
}

Когда он заканчивается, этот метод вызывается (он вызывается только ОДИН РАЗ, я дважды проверил это)

- (void)completeOperation {
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    executing = NO;
    completed = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

Но завершениеBlock вызывается дважды и дважды выводит «Completed» в консоль.

А вот и методы, указывающие на текущее состояние:

- (BOOL)isReady {
    if (executing || cancelled || completed) {
        return NO;
    }
    return YES;
}
- (BOOL)isCancelled {
    return cancelled;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return executing;
}

- (BOOL)isFinished {
    return completed;
}

isCancelled никогда не превращается в YES в моем тестовом коде, так что это не может быть причиной.

Я действительно не понимаю, почему завершение блока вызывается дважды. Даже при установке блока завершения на nil внутри блока завершения он иногда вызывается дважды, что еще более странно.


person JonasG    schedule 04.05.2013    source источник


Ответы (1)


Не уверен, что это причина, но по моему опыту нет необходимости переопределять свойства состояния только для чтения. Вы несете ответственность за периодическую проверку isCancelled в основном цикле и, если он установлен, выводит все, что вы делаете, но я считаю, что другие флаги состояния (isReady, isFinished, isExecuting) позаботятся автоматически.

Сколько раз он сработает, если вы уберете обработку флага состояния и просто выполните свой процесс в -main?

EDIT: если вы переопределяете эти флаги, чтобы разрешить параллелизм, вам следует прочитать примечания в документах

Судя по всему, вам никогда не придется переопределять isReady или isCancelled, а вместо этого переопределять -start в соответствии с инструкциями в документации.

person Henri Normak    schedule 05.05.2013
comment
Да, ты прав! Должно быть, я неправильно прочитал документы раньше. Теперь я запускаю собственный фоновый поток в -start и удалил переопределения isReady и isCancelled, и теперь он работает, блок завершения вызывается только один раз :) - person JonasG; 05.05.2013
comment
Я согласен, что вы не хотите реализовывать свои собственные методы isReady или isCancelled. Если вам нужен пользовательский isReady, вы должны вызвать super: как говорится в документации, если вы хотите использовать пользовательские условия для определения готовности вашего объекта операции, вы можете переопределить этот метод и вернуть значение, которое точно отражает готовность приемника . Если вы это сделаете, ваша пользовательская реализация должна вызвать super и включить его возвращаемое значение в состояние готовности объекта. Ваша пользовательская реализация также должна генерировать соответствующие уведомления KVO для пути ключа isReady. - person Rob; 26.05.2014