Як я можу визначити, чи до об'єкта приєднано спостерігач за ключовим значенням


142

якщо ви скажете об'єкту c вилучитиObservers: для ключового шляху та ключового шляху не було зареєстровано, він тріщить сум. подібно до -

"Неможливо видалити спостерігача для ключового шляху" theKeyPath ", оскільки він не зареєстрований як спостерігач."

чи є спосіб визначити, чи є об’єкт зареєстрованим спостерігачем, тож я можу це зробити

if (object has observer){
  remove observer
}
else{
  go on my merry way
}

Я потрапив у цей сценарій, оновивши старий додаток на iOS 8, де дислокувався контролер перегляду та кидав виняток "Неможливо видалити". Я подумав , що, зателефонувавши addObserver:в viewWillAppear:і , відповідно , removeObserver:в viewWillDisappear:, дзвінки були зістиковано. Мені потрібно швидко виправити, тому я збираюся реалізувати рішення про випробування та залишити коментар, щоб далі розслідувати причину.
bneely

Я просто маю справу з чимось подібним, і я бачу, що мені потрібно глибше заглянути в свою конструкцію і налаштувати її, щоб мені не потрібно було знову знімати спостерігача.
Богдан

використання значення bool, як запропоновано у цій відповіді, працювало найкраще для мене: stackoverflow.com/a/37641685/4833705
Lance Samaria

Відповіді:


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 мс на використання ... уявіть це у настільному огляді
Жоао Нунес

19
Не заперечуючи, тому що ви не хочете сказати, що це небезпечно для виробничого коду і, можливо, вийде з ладу в будь-який момент. Збільшення винятків за допомогою рамкового коду не є можливим для какао.
Микола Рухе

6
Як користуватися цим кодом у швидкій версії 2.1. зробіть {спробуйте self.playerItem? .removeObserver (self, forKeyPath: "статус")} вловлюйте помилку як NSError {print (error.localizedDescription)} отримуючи попередження.
Vipulk617

37

Справжнє питання, чому ви не знаєте, спостерігаєте ви це чи ні.

Якщо ви робите це в класі спостережуваного об'єкта, зупиніться. Незалежно від спостереження він очікує, що він продовжить його спостерігати. Якщо ви відключите спостереження від спостерігача без його відома, сподівайтеся, що все порушиться; Більш конкретно, сподівайтеся, що стан спостерігача стане невдалим, оскільки він не отримує оновлення від раніше спостережуваного об'єкта.

Якщо ви робите це в класі спостережуваного об'єкта, просто запам'ятайте, які об’єкти ви спостерігаєте (або, якщо ви коли-небудь спостерігали один об'єкт, чи ви його спостерігаєте). Це припущення, що спостереження є динамічним та між двома інакше не пов'язаними об'єктами; якщо спостерігачеві належить спостережуване, просто додайте спостерігача після створення або збереження спостережуваного та видаліть спостерігача перед тим, як звільнити спостережуваного.

Додавання та видалення об'єкта як спостерігача зазвичай має відбуватися в класі спостерігача, а ніколи в спостережуваному об'єкті.


14
Використовуйте випадок: ви хочете видалити спостерігачів у viewDidUnload, а також у dealloc. Це видалення їх двічі, і викине виняток, якщо ваш viewController буде вивантажений із попередження про пам'ять, а потім також випущений. Як ви пропонуєте вирішити цей сценарій?
bandejapaisa

2
@bandejapaisa: Досить те, що я сказав у своїй відповіді: Слідкуйте за тим, чи я спостерігаю, і лише намагайтеся припинити спостереження, якщо я є.
Пітер Хосей

41
Ні, це не цікаве питання. Вам не слід було б це слідкувати; ви повинні мати змогу просто скасувати реєстрацію всіх слухачів у взаємодії, не піклуючись про те, потрапили ви у шлях коду, де він доданий чи ні. Це має працювати як видалення NSNotificationCenter RemoveObserver, що не важливо, чи є у вас його чи ні. Цей виняток - це просто створення помилок там, де жоден інакше не існував би, що є поганим дизайном API.
Гленн Мейнард

1
@GlennMaynard: Як я вже говорив у відповіді: "Якщо ви відключите спостереження від спостерігача без його відома, сподівайтеся, що щось порушиться; Більш конкретно, сподівайтеся, що стан спостерігача стане невдалим, оскільки він не отримує оновлення від раніше спостережуваного об'єкта ". Кожен спостерігач повинен закінчити власне спостереження; невиконання цього в ідеалі має бути добре помітним.
Пітер Хосей

3
У запитанні нічого не йдеться про усунення інших спостерігачів коду.
Гленн Мейнард

25

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


Чи знаєте ви, як я можу знайти конкретного спостерігача? objectAtIndex:не дає бажаного результату.)
Еймантас

1
@MattDiPasquale Чи знаєте ви, як я можу прочитати спостереженняInfo в коді? У відбитках це виходить чудово, але це вказівник на недійсність. Як я повинен це прочитати?
neeraj

наблюдениеInfo - це метод налагодження, задокументований у папці налагодження Xcode (щось із "магією" у заголовку). Ви можете спробувати його шукати. Я можу сказати, що якщо вам потрібно знати, чи хтось спостерігає за вашим об’єктом - ви робите щось не так. Перегляньте свою архітектуру та логіку. Навчився цього важким шляхом.)
Еймантас

Джерело:NSKeyValueObserving.h
nefarianblack

плюс 1 за комічно глухий кут, але все-таки дещо корисна відповідь
Буде Фон Улріх

4

Єдиний спосіб зробити це - встановити прапор, коли ви додасте спостерігача.


3
Якщо ви, в кінцевому підсумку, з BOOLs всюди, краще все-таки створити 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];
    }
}

1
Якщо це трапляється у багатопотоковому світі, вам потрібно переконатися, що ваш масив є ThreadSafe
shrutim

Ви зберігаєте чітке посилання на об’єкт, що збільшуватиме кількість збереження кожного разу, коли об’єкт додається до списку, і не буде розміщений, якщо його посилання не буде видалено з масиву. Я вважаю за краще використовувати NSHashTable/ NSMapTableзберігати слабкі посилання.
atulkhatri

3

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

Як і зберегтиCount . Можливо 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:@""];
}

І вам потрібні певні перевірки - реалізуйте власний клас, який обробляє масив спостерігачів, і використовуйте його для своїх перевірок.


[self removeObserver:nil forKeyPath:@""]; потрібно йти раніше: [super viewWillDisappear:animated];
Джошуа Харт

@JoshuaHart чому?
quarezz

Тому що це метод розрушення (dealloc). Коли ви перекриєте якийсь метод схованки, ви називаєте супер останнім. Як: - (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: наблюдениеInfo повертає вказівник, який ідентифікує інформацію про всіх спостерігачів, зареєстрованих у приймачі.
FredericK

Це було краще сказано у відповіді @ mattdipasquale
Ben Leggiero

2

Вся суть в схемі спостерігача полягає в тому, щоб дозволити спостережуваному класу бути «запечатаним» - не знати або дбати про те, чи спостерігається він. Ви явно намагаєтесь зламати цю модель.

Чому?

Проблема у вас полягає в тому, що ви припускаєте, що вас спостерігають, коли вас немає. Цей об’єкт не почав спостереження. Якщо ви хочете, щоб ваш клас мав контроль над цим процесом, то вам слід розглянути можливість використання центру сповіщень. Таким чином ваш клас має повний контроль над тим, коли можна спостерігати дані. Отже, байдуже, хто дивиться.


10
Він запитує, як слухач може дізнатися, чи слухає він щось, а не як об'єкт, який спостерігається, може дізнатися, чи спостерігається він.
Гленн Мейнард

1

Я не прихильник цього рішення про те, що я намагаюся спіймати, тому що я роблю більшу частину часу, це те, що я створюю метод передплати та скасування підписки для певного сповіщення всередині цього класу. Наприклад, ці два методи підписують або скасовують підписку об'єкта на глобальне сповіщення про клавіатуру:

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

Всередині цих методів я використовую приватну власність, встановлену на істинну або помилкову залежно від стану підписки, наприклад:

@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);
}

1
Як божевільно, що це кидає виняток? Чому він просто не робить нічого, якщо нічого не прикріплюється?
Аран Малхолланд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.