如何判断对象是否附加了键值观察器


142

如果您告诉一个目标c对象removeObservers:对于一个关键路径而该关键路径尚未注册,这将使您难过。喜欢 -

“无法从中删除键路径“ theKeyPath”的观察者,因为它没有注册为观察者。

有没有一种方法可以确定一个对象是否具有注册的观察者,所以我可以这样做

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

我进入了这个场景,在iOS 8上更新了一个旧应用程序,在该应用程序中,分配了一个视图控制器,并引发了“无法删除”异常。我认为,通过调用addObserver:viewWillAppear:,相应removeObserver:viewWillDisappear:,该电话都正确配对。我必须进行快速修复,因此我将实现try-catch解决方案,并发表评论以进一步调查原因。
6

我只是在处理类似的问题,我发现我需要更深入地研究设计并进行调整,以免再次移除观察者。
Bogdan

使用此答案中建议的bool值最适合我:stackoverflow.com/a/37641685/4833705
Lance Samaria

Answers:


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
投票赞成我很可能会同意的已删除的rant。
Ben Gotow 2011年

12
这里还没有其他优雅的解决方案吗?这其中需要每使用至少2毫秒...想象它在tableviewcell
若奥·努涅斯

19
投反对票的原因是您忽略了这对于生产代码不安全,并且随时可能失败。通过框架代码引发异常不是Cocoa中的选择。
Nikolai Ruhe

6
在Swift 2.1中如何使用此代码。做{试试self.playerItem?.removeObserver(self,forKeyPath:“ status”)}捕获让错误,因为NSError {print(error.localizedDescription)}得到警告。
Vipulk617

37

真正的问题是为什么您不知道自己是否正在观察。

如果要在要观察的对象的类中执行此操作,请停止。无论观察到什么,它都希望继续观察它。如果您在不知情的情况下中断了观察者的通知,则可能会遇到问题。更具体地说,因为观察者的状态没有收到来自先前观察到的对象的更新,所以它会过时。

如果您在观察对象的类中执行此操作,则只需记住要观察的对象(或者,如果您只观察一个对象,则是否在观察)。这是假定观察是动态的,并且在两个其他不相关的对象之间;如果观察者拥有观察者,则只需在创建或保留观察者之后添加观察者,然后在释放观察者之前删除观察者。

作为观察者添加和删除对象通常应该在观察者的类中发生,而不应该在被观察对象的类中发生。


14
用例:您想在viewDidUnload和dealloc中删除观察者。这将它们删除两次,并且如果从内存警告中卸载了viewController,然后又将其释放,则会引发异常。您如何建议处理这种情况?
bandejapaisa 2012年

2
@bandejapaisa:我在回答中说的差不多:跟踪我是否在观察,如果有,请尝试停止观察。
彼得·霍西

41
不,这不是一个有趣的问题。您不必跟踪此情况。您应该能够简单地在dealloc中注销所有侦听器,而不必关心是否碰巧添加了代码的路径。它应该像NSNotificationCenter的removeObserver一样工作,它并不关心您是否真正拥有它。这种例外只是在其他情况下根本不会存在错误,这是错误的API设计。
Glenn Maynard

1
@GlennMaynard:就像我在回答中说的那样:“如果您在观察员的通知不知不觉的情况下切断了它的通知,那就希望事情会破裂;更具体地说,因为观察者的状态没有收到来自先前观察到的对象的更新,所以它会过时。” 每个观察者都应结束自己的观察;失败的做法在理想情况下应该非常明显。
彼得·霍西

3
问题中没有任何内容涉及删除其他代码的观察者。
Glenn Maynard

25

FWIW,[someObject observationInfo]似乎nil如果someObject没有任何观察员。但是,我不相信这种行为,因为我没有看到它的记录。另外,我不知道如何阅读observationInfo才能获得特定的观察者。


您是否知道我该如何检索特定的观察者?objectAtIndex:不会产生预期的结果。)
Eimantas 2012年

1
@MattDiPasquale您知道如何读取代码中的observationInfo吗?在印刷品中效果不错,但这是指向无效的指针。我应该怎么读?
neeraj 2013年

observationInfo是Xcode调试文件中记录的调试方法(标题中带有“ magic”的东西)。您可以尝试查找。我可以告诉您,如果您需要知道某人是否正在观察您的物体-您做错了什么。重新考虑您的架构和逻辑。艰难地学习了。)
Eimantas 2014年

资料来源:NSKeyValueObserving.h
nefarianblack

加1可笑地死了,但仍然有一定帮助
威尔·冯·乌尔里希

4

唯一的方法是在添加观察者时设置标志。


3
这样最终会到处都是BOOL,最好还是创建一个KVO包装对象,该对象可以处理添加观察者和移除观察者的问题。它可以确保您的观察者仅被移除一次。我们像这样使用一个对象,并且它可以工作。
bandejapaisa 2012年

好主意,如果您不总是观察。
安德烈·西蒙

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 2016年

您将保留一个对象的强引用,这将在每次将对象添加到列表中时增加保留计数,除非将其引用从数组中删除,否则不会释放该对象。我希望使用NSHashTable/ NSMapTable保留弱引用。
atulkhatri

3

我认为-这与keepCount机制类似。您无法确定当前是否有观察员。即使您检查:self.observationInfo-您也无法确定将来是否会有观察者。

就像keepCount一样。也许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)。当您覆盖某种拆卸方法时,您将调用super last。像: - (void) setupSomething { [super setupSomething]; … } - (void) tearDownSomething { … [super tearDownSomething]; }
约书亚·哈特

viewWillDisapear不是拆卸方法,它与dealloc没有关系。如果向前推送到导航堆栈,则将调用viewWillDisapear,但是视图将保留在内存中。我知道安装/拆卸的逻辑在哪里,但是在这里这样做并没有任何实际好处。仅当您在基类中具有某些逻辑时才希望将删除放置在super之前,这可能会与当前的观察者发生冲突。
quarezz

3

[someObject observationInfo]nil如果没有观察者,则返回。

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

}

根据Apple docs:observationInfo返回一个指针,该指针标识有关向接收者注册的所有观察者的信息。
FredericK


2

观察者模式的全部目的是允许“观察”类“密封”-不知道或不在乎是否正在观察它。您正在明确尝试打破这种模式。

为什么?

您遇到的问题是您假设自己没有被观察到。该对象未开始观察。如果您希望您的班级控制此过程,则应考虑使用通知中心。这样,您的班级可以完全控制何时可以观察到数据。因此,它不在乎谁在看。


10
他问的是,听众如何才能知道自己是否在听某物,而不是被观察到的对象如何才能发现自己是否正在被观察。
格伦·梅纳德

1

我不是那种尝试捕获解决方案的粉丝,所以我大部分时间都在为该类中的特定通知创建订阅和取消订阅方法。例如,以下两种方法将对象订阅或取消订阅全局键盘通知:

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

1
抛出异常有多疯狂?如果没有附件,为什么不做任何事情呢?
亚兰·穆赫兰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.