Как отлаживать КВО - PullRequest
       9

Как отлаживать КВО

4 голосов
/ 11 ноября 2011

В моей программе я использую KVO вручную, чтобы наблюдать изменения в значениях свойств объекта.Я получаю сигнал EXC_BAD_ACCESS в следующей строке кода внутри пользовательского установщика:

[self willChangeValueForKey:@"mykey"];

Странно то, что это происходит, когда фабричный метод вызывает пользовательский установщик, и вокруг не должно быть никаких наблюдателей.,Я не знаю, как отладить эту ситуацию.

Обновление: Путь к списку всех зарегистрированных наблюдателей - observationInfo.Оказалось, что в списке действительно есть объект, указывающий на неверный адрес.Тем не менее, я вообще не знаю как он туда попал.

Обновление 2: По-видимому, один и тот же объект и обратный вызов метода могут быть зарегистрированы несколько раз для данногообъект - в результате идентичные записи в наблюдаемом объекте observationInfo.При удалении регистрации удаляется только одна из этих записей.Это поведение немного нелогично (и, конечно, это ошибка в моей программе вообще добавлять несколько записей), но это не объясняет, как ложные наблюдатели могут таинственным образом отображаться в недавно выделенных объектах (если не происходит некоторое кеширование / повторное использование).о чем я не знаю).

Измененный вопрос: Как я могу выяснить, ГДЕ и КОГДА объект был зарегистрирован в качестве наблюдателя?

Обновление3: Специальный пример кода.

ContentObj - это класс со словарем в качестве свойства с именем mykey.Он переопределяет:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"mykey"]) {
        automatic = NO;
    } else {
        automatic=[super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

Пара свойств имеет методы получения и установки следующим образом:

- (CGFloat)value {
    return [[[self mykey] objectForKey:@"value"] floatValue];
}
- (void)setValue:(CGFloat)aValue {
    [self willChangeValueForKey:@"mykey"];
    [[self mykey] setObject:[NSNumber numberWithFloat:aValue]
                     forKey:@"value"];
    [self didChangeValueForKey:@"mykey"];
}

Класс контейнера имеет свойство contents класса NSMutableArray, которое содержит экземплярыкласс ContentObj.У него есть несколько методов, которые обрабатывают регистрации вручную:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"contents"]) {
        automatic = NO;
    } else {
        automatic=[super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

- (void)observeContent:(ContentObj *)cObj {
    [cObj addObserver:self
           forKeyPath:@"mykey"
              options:0
              context:NULL];
}

- (void)removeObserveContent:(ContentObj *)cObj {
    [cObj removeObserver:self
              forKeyPath:@"mykey"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    if (([keyPath isEqualToString:@"mykey"]) &&
        ([object isKindOfClass:[ContentObj class]])) {
        [self willChangeValueForKey:@"contents"];
        [self didChangeValueForKey:@"contents"];
    }
}

В классе контейнера есть несколько методов, которые изменяют contents.Они выглядят следующим образом:

- (void)addContent:(ContentObj *)cObj {
    [self willChangeValueForKey:@"contents"];
    [self observeDatum:cObj];
    [[self contents] addObject:cObj];
    [self didChangeValueForKey:@"contents"];
}

и несколько других, которые предоставляют функциональность, аналогичную массиву.Все они работают, добавляя / удаляя себя в качестве наблюдателей.Очевидно, что все, что приводит к множественным регистрациям, является ошибкой и может скрываться в этих методах где-то .

Мой вопрос нацелен на стратегии по отладке подобного рода ситуаций.В качестве альтернативы, пожалуйста, не стесняйтесь предоставить альтернативную стратегию для реализации этого типа шаблона уведомления / наблюдателя.

Обновление 4: Я нашел ошибку, используя смесь точек останова, NSLog s,Кодовые обзоры и потоотделение.Я не использовал контекст в KVO, хотя это определенно еще одно полезное предложение.Это была действительно неправильная двойная регистрация, которая - по причинам, не зависящим от меня - привела к наблюдаемому поведению.

Реализация, включающая [self willChange...]; [self didChange...], работает так, как описано (на iOS 5), хотя она далеко не прекрасна.Проблема в том, что, поскольку NSArray не является KVO-совместимым, нет возможности говорить об изменениях в его содержимом.Я также подумал об уведомлениях, предложенных Майком Эшем, но я решил пойти с KVO, так как это выглядело как более Cocoa -ишевой механизм для выполнения работы.Возможно, это было не лучшее решение ...

Ответы [ 2 ]

6 голосов
/ 11 ноября 2011

Да, вызов -addObserver: дважды приведет к двум регистрациям.Класс Foo и некоторый подкласс Foo, Bar, могут (законно) регистрироваться для одного и того же уведомления, но с разными контекстами (всегда включать контекст, всегда проверять контекст в -observeValueForKeyPath и всегда вызывать super в -observeValueForKeyPath).

Это означает, что экземпляр Bar будет зарегистрирован дважды, и это правильно.

Однако вы почти наверняка не захотите регистрировать один и тот же объект / путь / контекст несколько раз случайно.и, как говорит @wbyoung, переопределение -addObserver:forKeyPath:options:context: должно помочь вам убедиться, что этого не произойдет.Если необходимо отслеживать наблюдателей / путь к ключу / контекст в массиве и удостовериться, что они уникальны.

У Майка Эша есть несколько интересных мыслей и кода в своем блоге об использовании контекстов .Он прав в том, что он сломан, но на практике КВО идеально подходит для использования.

То есть, когда вы используете его, чтобы сделать что-то, оно предназначено для этого.Раньше было так, что вы абсолютно не могли делать что-то подобное ..

[self willChangeValueForKey:@"contents"];
[self didChangeValueForKey:@"contents"];

, потому что это ложь.Значение «содержимого» при вызове -willChange.. должно отличаться от значения при вызове -didChange...Механизм KVO вызовет -valueForKey:@"contents" в -willChangeValueForKey и -didChangeValueForKey, чтобы проверить, изменилось ли значение.Это очевидно не будет работать с массивом, так как независимо от того, как вы изменяете содержимое, у вас остается тот же объект.Теперь я не знаю, так ли это до сих пор (веб-поиск ничего не дал), но учтите, что -willChangeValueForKey, -didChangeValueForKey - неправильный способ обработки ручного kvo коллекции. Для этого Apple предоставляет альтернативуметоды: -

– willChange:valuesAtIndexes:forKey:
– didChange:valuesAtIndexes:forKey:
– willChangeValueForKey:withSetMutation:usingObjects:
– didChangeValueForKey:withSetMutation:usingObjects:

Может все еще не быть правдой, что значение должно измениться, но если это так, ваша схема не будет работать.

То, что я хотел бы сделать, это иметь одинуведомление об изменениях в вашей коллекции.И другое уведомление для модификации предметов в этой коллекции.то есть в тот момент, когда вы пытаетесь вызвать уведомления для @ "contents", тогда вместо этого у вас могут быть @ "contents" и @ "propertiesOfContents".Вам нужно будет соблюдать два ключевых пути, но вы можете использовать автоматический kvo вместо ручного запуска уведомлений.(Использование автоматического kvo гарантирует, что будут вызваны правильные версии -willChange.. -didChange..)

Для автоматического kvo массива посмотрите (не требуется NSArrayController): - Key-Value-Observing a to-много отношения в Какао

Затем каждый раз, когда элемент добавляется в коллекцию, наблюдайте за необходимыми вам свойствами (как вы делаете сейчас), и когда они меняются, меняйте значение на self.propertiesOfContents.(Хорошо, когда я прочитал, что это не обязательно звучит менее навязчиво, чем ваше решение, но я все еще верю, что оно может вести себя лучше).

4 голосов
/ 11 ноября 2011

В ответ на ваш измененный вопрос попробуйте переопределить addObserver: forKeyPath: options: context: в своем пользовательском классе и установить для него точку останова.В качестве альтернативы вы можете просто установить символическую точку останова на -[NSObject addObserver:forKeyPath:options:context:], но это, вероятно, сильно ударит.

...