Почему NSOperation отключает автоматическое наблюдение за значением ключа?

При работе с пользовательским подклассом NSOperation я заметил, что автоматическое наблюдение за значением ключа отключено методом класса [NSOperation automaticallyNotifiesObserversForKey] (который возвращает NO по крайней мере для некоторых ключевых путей). Из-за этого код внутри подклассов NSOperation замусорен ручными вызовами willChangeValueForKey: и didChange…, что видно во многих примерах кода в Интернете.

Почему NSOperation это делает? С автоматической поддержкой KVO люди могут просто объявлять свойства для флагов жизненного цикла операции (isExecuting и т. д.) и запускать события KVO через средства доступа, т.е. следующий код:

[self willChangeValueForKey:@"isExecuting"];
executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];

…можно заменить на это:

[self setIsExecuting:NO];
[self setIsFinished:YES];

Где-то есть подвох? Я просто изменил automaticallyNotifiesObserversForKey, чтобы вернуть YES, и все работает нормально.


person zoul    schedule 26.08.2010    source источник


Ответы (4)


Наиболее вероятное объяснение состоит в том, что ключи kvo не соответствуют стандартным соглашениям. Обычно есть такие методы, как -isExecuting и -setExecuting:, где ключевой путь — @"executing". В случае NSOperation путь к ключу — @"isExecuting".

Другая возможность заключается в том, что большинство NSOperations на самом деле не имеют метода с именем -setIsExecuting: для изменения этого значения. Вместо этого они основывают флаги выполнения/завершения на другом внутреннем состоянии. В этом случае абсолютно необходимо использовать явные уведомления willChange/didChange. Например, если у меня есть NSOperation, которая обертывает NSURLConnection, у меня может быть 2 ivars, одна с именем data, которая содержит загруженные данные, и одна с именем connection, которая содержит NSURLConnection, и я могу реализовать геттеры следующим образом:

- (BOOL)isExecuting {
    return (connection != nil);
}

- (BOOL)isFinished {
    return (data != nil && connection == nil);
}

Теперь мой метод -start может использовать

[self willChangeValueForKey:@"isExecuting"];
data = [[NSMutableData alloc] init]; // doesn't affect executing, but is used later
connection = [[NSURLConnection connectionWithRequest:request delegate:self] retain];
[self didChangeValueForKey:@"isExecuting"];

начать выполнение и

[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
[connection cancel];
[connection release];
connection = nil;
[self didChangeValueForKey:@"isFinished"];
[self didChangeValueForKey:@"isExecuting"];

заканчивать.

person Lily Ballard    schedule 21.01.2011
comment
Кевин, я считаю, что ваш пример непоследовательный и не должен связываться после отмены/выпуска. В противном случае isFinished не вернет YES. - person Steven Fisher; 03.05.2012

Хотя я согласен с тем, что переопределение automaticallyNotifiesObserversForKey работает, но лично я полностью отказываюсь от свойств isExecuting и isFinished и вместо этого определяю свойства executing и finished, что, как предлагает Кевин, больше соответствует современным соглашениям:

@property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
@property (nonatomic, readwrite, getter = isFinished)  BOOL finished;

Затем я пишу настраиваемые сеттеры для этих двух свойств, которые выполняют необходимые уведомления isExecuting и isFinished:

- (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

Это дает:

  • более привычное объявление свойства BOOL;
  • пользовательские сеттеры удовлетворяют странным уведомлениям, которые требует NSOperation; а также
  • Теперь я могу просто использовать сеттеры executing и finished во всей реализации операции, не засоряя свой код уведомлениями.

Должен признаться, что мне нравится элегантность переопределения automaticallyNotifiesObserversForKey, но я просто беспокоюсь о непредвиденных последствиях.


Обратите внимание: если вы делаете это в iOS 8 или Yosemite, вам также придется явно синтезировать эти свойства в вашем @implementation:

@synthesize finished  = _finished;
@synthesize executing = _executing;
person Rob    schedule 11.08.2013

Я не знаю, почему вы говорите о том, что NSOperation не может использовать автоматический КВО. Но я просто пытаюсь это проверить, поэтому он может использовать KVO.

[self addObserver:self
       forKeyPath:@"isReady"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

[self addObserver:self
       forKeyPath:@"isExecuting"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

[self addObserver:self
       forKeyPath:@"isFinished"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

[self addObserver:self
       forKeyPath:@"isCancelled"
          options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
          context:&ctxKVO_CSDownloadOperation];

...

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == &ctxKVO_CSDownloadOperation) {
        NSLog(@"KVO: %@", keyPath);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

Результат:

2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isExecuting : 0
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] KVO: isExecuting
2017-08-02 14:29:58.831 CSDownloader[77366:5089399] isFinished : 0
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isFinished
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] isCancelled : 0
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isCancelled
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.832 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isExecuting : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isExecuting
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isFinished : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isFinished
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] isCancelled : 0
2017-08-02 14:29:58.833 CSDownloader[77366:5089399] KVO: isCancelled
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] isReady : 1
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] KVO: isReady
2017-08-02 14:29:58.834 CSDownloader[77366:5089399] isExecuting : 0

Так что я действительно смущен этим вопросом и ответами...

person Chris Forever    schedule 02.08.2017

NSOperationQueue не наблюдает за isFinished или isExecuting, он наблюдает за finished и executing.

isFinished — это просто синтезированный метод доступа get для свойства finished. Для этого свойства будут отправляться автоматические уведомления о наблюдении за значением ключа, если только ваш подкласс специально не отказался от автоматических уведомлений KVO, внедрив +automaticallyNotifiesObserversForKey или +automaticallyNotifiesObserversOf<Key> для возврата NO. Если вы не отключили автоматические уведомления KVO, вам не нужно выполнять уведомления вручную с помощью will/DidChangeValueForKey:. В вашем случае вы вручную отправляли уведомления для isFinished и isExecuting, которые не являются ключевыми путями, которые наблюдает NSOperationQueue.

Вкратце: это не те ключевые пути, которые ищет NSOperationQueue.

Это не те ключевые пути, которые вы ищете

executing и finished являются правильными путями ключей, и они должны отправлять автоматические уведомления KVO.

Если вы по-настоящему параноидально относитесь к KVO и хотите отправлять уведомления для путей получения ключей доступа, таких как isFinished, зарегистрируйте свое свойство как зависимость пути ключа:

+ (NSSet *) keyPathsForValuesAffectingIsFinished {
    NSSet   *result = [NSSet setWithObject:@"finished"];
    return result;
}
person quellish    schedule 20.09.2014
comment
Вы не можете использовать на нас свои джедайские трюки с разумом. NSOperation и NSOperationQueue на самом деле действительно соблюдают isFinished/isExecuting (как правильно описано в документации). Это легко продемонстрировать. Похоже, вы делаете неверный вывод из свойств finished и executing в NSOperation.h этот NSOperation/NSOperationQueue внутренне должен наблюдать finished и executing КВН. isFinished/isExecuting КВН не для параноиков, а просто незаменим для всех, кто использует асинхронные операции. - person Rob; 23.09.2014
comment
Пожалуйста, обратитесь к документации по соблюдению кодирования значения ключа и соблюдению значения ключа. Правильный способ обеспечить соответствие значению ключа в этом сценарии — зарегистрировать свойство как зависимость от пути ключа получателя. Если вы утверждаете, что KVO или NSOperation не работают или имеют плохой API, сообщите об ошибке. - person quellish; 23.09.2014
comment
Это уже третий раз, когда вы просите меня сообщить об ошибке, потому что NSOperation ведет себя точно так, как описано в Руководстве по программированию с параллелизмом. Lol. Конечно, разочаровывает то, что давняя конвенция Apple о KVN для NSOperation требует этого нестандартного ручного уведомления об изменении, и если вас беспокоит, что оно не соответствует традиционному KVO-соответствию, вы должны сообщить об ошибке. Но пока Apple не изменит его, пожалуйста, перестаньте советовать людям не делать соответствующие уведомления isFinished/isExecuting для NSOperation, потому что это вызывает проблемы для параллельных/асинхронных операций. - person Rob; 23.09.2014
comment
Конечно, разочаровывает то, что давняя конвенция Apple о КВН для NSOperation требует этого нестандартного ручного уведомления об изменении. Это абсолютно неправда, но если у вас есть проблемы с документацией или API, сообщите об ошибке. Если вы считаете, что NSOperation не совместим с KVO, что вы и утверждаете, сообщите об ошибке. - person quellish; 23.09.2014
comment
прекратите советовать людям не делать соответствующие уведомления isFinished/isExecuting. Я советую людям использовать KVO правильно и при переопределении общедоступных свойств только для чтения свойствами с возможностью записи регистрировать пути зависимых ключей, а НЕ отправлять уведомления об изменении вручную. Опять же, это описано в документации KVO. - person quellish; 23.09.2014
comment
Хорошее дополнение шаблона зависимостей ключевого пути для получения необходимых isFinished (и других) уведомлений. - person Rob; 23.09.2014