Почему NSOperation отключает автоматическое наблюдение значения ключа? - PullRequest
19 голосов
/ 26 августа 2010

При работе с пользовательским подклассом 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, и кажется, что все работает нормально.

Ответы [ 4 ]

17 голосов
/ 21 января 2011

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

Другая возможность состоит в том, что большинство операций NSO фактически не имеют метода с именем -setIsExecuting: для изменения этого значения.Вместо этого они основывают флаги выполнения / завершения на другом внутреннем состоянии.В этом случае абсолютно необходимо использовать явные уведомления willChange / didChange.Например, если у меня есть операция NSO, которая оборачивает 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"];

для завершения.

6 голосов
/ 12 августа 2013

Хотя я согласен с тем, что переопределение 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;
0 голосов
/ 02 августа 2017

Я не знаю, почему вы говорите о 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

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

0 голосов
/ 21 сентября 2014

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

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

TL; DR: это не ключевые пути, которые ищет NSOperationQueue.

These are not the key paths you are looking for

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

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

+ (NSSet *) keyPathsForValuesAffectingIsFinished {
    NSSet   *result = [NSSet setWithObject:@"finished"];
    return result;
}
...