Как я могу сказать, если к объекту прикреплен наблюдатель значения ключа

142

если вы указываете объекту c объект removeObservers: для ключевого пути, и этот ключевой путь не зарегистрирован, он взламывает sads. лайк -

«Невозможно удалить наблюдателя для ключевого пути« theKeyPath », поскольку он не зарегистрирован в качестве наблюдателя».

Есть ли способ определить, есть ли у объекта зарегистрированный наблюдатель, так что я могу сделать это

if (object has observer){
  remove observer
}
else{
  go on my merry way
}
Аран Малхолланд
источник
Я попал в этот сценарий, обновляя старое приложение на iOS 8, где контроллер представления был освобожден, и выбрасывал исключение «Не удается удалить». Я подумал , что, позвонив addObserver:в viewWillAppear:и , соответственно , removeObserver:в viewWillDisappear:, звонки были состыкованы. Я должен сделать быстрое исправление, поэтому я собираюсь реализовать решение try-catch и оставить комментарий для дальнейшего изучения причины.
Bneely
Я просто имею дело с чем-то похожим, и я вижу, что мне нужно более глубоко изучить мой дизайн и настроить его так, чтобы мне больше не пришлось удалять наблюдателя.
Богдан
использование значения bool, подобного предложенному в этом ответе, сработало для меня лучше всего: stackoverflow.com/a/37641685/4833705
Ланс Самария,

Ответы:

315

Сделайте попытку поймать ваш вызов removeObserver

@try{
   [someObject removeObserver:someObserver forKeyPath:somePath];
}@catch(id anException){
   //do nothing, obviously it wasn't attached because an exception was thrown
}
Адам
источник
12
1+ Хороший ответ, работал для меня, и я согласен с вашей напыщенной работой до того, как она была отредактирована
Роберт
25
проголосовал за удаленную тираду, с которой я бы, скорее всего, согласился.
Бен Готоу
12
Разве здесь нет другого элегантного решения? для этого требуется не менее 2 мс на одно использование ... представьте, что это в ящике стола
João Nunes
19
Принято во внимание, потому что вы не говорите, что это небезопасно для производственного кода и может привести к сбою в любое время. Вызывать исключения через код платформы не вариант в Какао.
Николай Рюэ
6
Как использовать этот код в Swift 2.1. do {try self.playerItem? .removeObserver (self, forKeyPath: "status")} перехватывает ошибку let как NSError {print (error.localizedDescription)}, получая предупреждение.
Vipulk617
37

Реальный вопрос в том, почему вы не знаете, соблюдаете ли вы это или нет.

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

Если вы делаете это в классе объекта наблюдения, просто запомните, какие объекты вы наблюдаете (или, если вы когда-либо наблюдаете только один объект, наблюдаете ли вы его). Это предполагает, что наблюдение является динамическим и находится между двумя иначе не связанными объектами; если наблюдатель владеет наблюдаемым, просто добавьте наблюдателя после того, как вы создадите или сохраните наблюдаемое, и удалите наблюдателя, прежде чем выпускать наблюдаемое.

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

Питер Хоси
источник
14
Вариант использования: вы хотите удалить наблюдателей в viewDidUnload, а также в dealloc. Это удалит их дважды и вызовет исключение, если ваш viewController выгружается из памяти, а затем также освобождается. Как вы предлагаете справиться с этим сценарием?
bandejapaisa
2
@bandejapaisa: В значительной степени то, что я сказал в своем ответе: следите за тем, наблюдаю ли я, и стараюсь только прекратить наблюдение, если я так делаю.
Питер Хоси
41
Нет, это не интересный вопрос. Вы не должны отслеживать это; у вас должна быть возможность просто отменить регистрацию всех слушателей в dealloc, не заботясь о том, случайно ли вы нашли путь к коду, где он был добавлен, или нет. Он должен работать как removeObserver NSNotificationCenter, который не заботится о том, есть ли у вас его на самом деле или нет. Это исключение просто создает ошибки, в которых иначе не было бы ничего, что является плохим дизайном API.
Гленн Мейнард
1
@GlennMaynard: Как я сказал в ответе: «Если вы отключите уведомления наблюдателя без его ведома, ожидайте, что что-то сломается; более конкретно, ожидайте, что состояние наблюдателя устареет, поскольку оно не получает обновлений от ранее наблюдавшегося объекта ». Каждый наблюдатель должен закончить свое собственное наблюдение; неспособность сделать это в идеале должна быть очень заметна.
Питер Хоси
3
Ничто в этом вопросе не говорит об удалении наблюдателей другого кода.
Гленн Мейнард
25

FWIW, [someObject observationInfo]кажется, nilесли someObjectнет наблюдателей. Однако я бы не стал доверять такому поведению, поскольку я не видел его документированным. Кроме того, я не знаю, как читать, observationInfoчтобы получить конкретных наблюдателей.

ma11hew28
источник
Вы случайно не знаете, как я могу получить конкретного наблюдателя? objectAtIndex:не дает желаемого результата.)
Eimantas
1
@MattDiPasquale Знаете ли вы, как я могу прочитать наблюдение в коде? В печатных изданиях это выходит хорошо, но это указатель на пустоту. Как я должен это прочитать?
neeraj
наблюдениеInfo - метод отладки, описанный в отладочной статье XCode (что-то с «магией» в заголовке). Вы можете попробовать поискать это. Я могу сказать, что если вам нужно знать, наблюдает ли кто-то за вашим объектом - вы делаете что-то не так. Переосмыслите свою архитектуру и логику. Узнал это трудным путем.)
Eimantas
Источник:NSKeyValueObserving.h
nefarianblack
плюс 1 за комичный тупик, но все же несколько полезный ответ
Уилл фон Ульрих
4

Единственный способ сделать это - установить флаг при добавлении наблюдателя.

Leibowitzn
источник
3
Если у вас везде BOOL, лучше создать объект-оболочку KVO, который обрабатывает добавление наблюдателя и его удаление. Это может гарантировать, что ваш наблюдатель удаляется только один раз. Мы использовали такой же объект, и он работает.
bandejapaisa
отличная идея, если вы не всегда соблюдаете.
Андре Симон
4

Когда вы добавляете наблюдателя к объекту, вы можете добавить его к NSMutableArrayэтому:

- (void)addObservedObject:(id)object {
    if (![_observedObjects containsObject:object]) {
        [_observedObjects addObject:object];
    }
}

Если вы хотите увидеть объекты, вы можете сделать что-то вроде:

for (id object in _observedObjects) {
    if ([object isKindOfClass:[MyClass class]]) {
        MyClass *myObject = (MyClass *)object;
        [self unobserveMethod:myObject];
    }
}
[_observedObjects removeAllObjects];

Помните, если вы не наблюдаете один объект, удалите его из _observedObjectsмассива:

- (void)removeObservedObject:(id)object {
    if ([_observedObjects containsObject:object]) {
        [_observedObjects removeObject:object];
    }
}
Oritm
источник
1
Если это происходит в многопоточном мире, вам нужно убедиться, что ваш массив является ThreadSafe
shrutim
Вы сохраняете строгую ссылку на объект, что будет увеличивать счет сохранения каждый раз, когда объект добавляется в список, и не будет освобожден, пока его ссылка не будет удалена из массива. Я бы предпочел использовать NSHashTable/, NSMapTableчтобы сохранить слабые ссылки.
atulkhatri
3

На мой взгляд - это похоже на механизм retainCount. Вы не можете быть уверены, что в текущий момент у вас есть наблюдатель. Даже если вы проверите: self.observationInfo - вы не можете точно знать, что у вас будут / не будут наблюдатели в будущем.

Нравится retainCount . Может быть, observationInfo метод не совсем такого рода бесполезна, но я только использовать его в целях отладки.

Так что в результате - вам просто нужно сделать это, как в управлении памятью. Если вы добавили наблюдателя - просто удалите его, когда он вам не нужен. Как использование методов viewWillAppear / viewWillDisappear и т. Д. Например:

-(void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self addObserver:nil forKeyPath:@"" options:NSKeyValueObservingOptionNew context:nil];
}

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self removeObserver:nil forKeyPath:@""];
}

И если вам нужны определенные проверки - реализуйте свой собственный класс, который обрабатывает массив наблюдателей, и используйте его для своих проверок.

quarezz
источник
[self removeObserver:nil forKeyPath:@""]; нужно идти раньше: [super viewWillDisappear:animated];
Джошуа Харт
@ JoshuaHart почему?
quarezz
Потому что это метод сноса (dealloc). Когда вы переопределяете какой-то метод разрыва, вы вызываете super last. Как: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
Джошуа Харт
viewWillDisapear не является методом разрыва и не имеет никакого отношения к dealloc. Если вы перейдете к стеку навигации, будет вызван viewWillDisapear , но ваше представление останется в памяти. Я вижу, куда вы идете с логикой настройки / демонтажа, но делать это здесь не даст никакой реальной выгоды. Вы хотели бы поместить удаление перед супер, только если у вас есть некоторая логика в базовом классе, которая может конфликтовать с текущим наблюдателем.
Quarezz
3

[someObject observationInfo]Вернитесь, nilесли нет наблюдателя.

if ([tableMessage observationInfo] == nil)
{
   NSLog(@"add your observer");
}
else
{
  NSLog(@"remove your observer");

}
Анупама
источник
В соответствии с Apple docs: surveyInfo возвращает указатель, который идентифицирует информацию обо всех наблюдателях, которые зарегистрированы получателем.
FredericK
Это было лучше, - сказал в ответе @ mattdipasquale
Бен Легжеро
2

Весь смысл модели наблюдателя состоит в том, чтобы позволить наблюдаемому классу быть «запечатанным» - не знать или не заботиться о том, наблюдается ли он. Вы явно пытаетесь сломать эту модель.

Зачем?

Проблема, с которой вы сталкиваетесь, заключается в том, что вы предполагаете, что за вами наблюдают, а за вами нет. Этот объект не начал наблюдение. Если вы хотите, чтобы ваш класс контролировал этот процесс, вам следует рассмотреть возможность использования центра уведомлений. Таким образом, ваш класс полностью контролирует, когда можно наблюдать данные. Следовательно, все равно, кто смотрит.

adonoho
источник
10
Он спрашивает, как слушатель может узнать, слушает ли он что-то, а не то, как наблюдаемый объект может узнать, наблюдается ли он.
Гленн Мейнард
1

Я не фанат этого решения catch catch, поэтому большую часть времени я занимаюсь созданием метода подписки и отмены подписки для конкретного уведомления внутри этого класса. Например, эти два метода подписывают или отменяют подписку объекта на глобальное уведомление клавиатуры:

@interface ObjectA : NSObject
-(void)subscribeToKeyboardNotifications;
-(void)unsubscribeToKeyboardNotifications;
@end

Внутри этих методов я использую приватное свойство, которое имеет значение true или false в зависимости от состояния подписки, например:

@interface ObjectA()
@property (nonatomic,assign) BOOL subscribedToKeyboardNotification
@end

@implementation

-(void)subscribeToKeyboardNotifications {
    if (!self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onKeyboardHide:) name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = YES;
    }
}

-(void)unsubscribeToKeyboardNotifications {
    if (self.subscribedToKeyboardNotification) {
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
        self.subscribedToKeyboardNotification = NO;
    }
}
@end
Себастьян Болдт
источник
0

В дополнение к ответу Адама я хотел бы предложить использовать такой макрос

#define SafeRemoveObserver(sender, observer, keyPath) \
@try{\
   [sender removeObserver:observer forKeyPath:keyPath];\
}@catch(id anException){\
}

пример использования

- (void)dealloc {
    SafeRemoveObserver(someObject, self, somePath);
}
Wattson
источник
1
Насколько сумасшедший, что это бросает исключение? Почему он просто ничего не делает, если ничего не присоединяется?
Аран Малхолланд