我如何判断一个对象是不是附加了键值观察器

Posted

技术标签:

【中文标题】我如何判断一个对象是不是附加了键值观察器【英文标题】:How can i tell if an object has a key value observer attached我如何判断一个对象是否附加了键值观察器 【发布时间】:2010-12-07 15:09:21 【问题描述】:

如果你告诉一个客观的c对象removeObservers:对于一个关键路径并且该关键路径没有被注册,它会破解悲伤。喜欢-

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

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

if (object has observer)
  remove observer

else
  go on my merry way

【问题讨论】:

我在 ios 8 上更新旧应用程序时遇到了这种情况,其中视图控制器正在被释放并抛出“无法删除”异常。我认为通过在viewWillAppear: 中调用addObserver: 并在viewWillDisappear: 中相应地调用removeObserver:,呼叫已正确配对。我必须快速修复,所以我将实施 try-catch 解决方案并发表评论以进一步调查原因。 我只是在处理类似的事情,我发现我需要更深入地研究我的设计并对其进行调整,这样我就不需要再次移除观察者了。 使用这个答案中建议的布尔值对我来说效果最好:***.com/a/37641685/4833705 【参考方案1】:

真正的问题是为什么你不知道你是否在观察它。

如果您在被观察对象的类中执行此操作,请停止。无论在观察什么,它都希望继续观察它。如果您在观察者不知情的情况下切断了观察者的通知,预计事情会中断;更具体地说,预计观察者的状态会过时,因为它没有从以前观察到的对象接收更新。

如果您在观察对象的类中执行此操作,只需记住您正在观察哪些对象(或者,如果您只观察一个对象,无论您是否正在观察它)。这是假设观察是动态的并且在两个原本不相关的对象之间;如果观察者拥有被观察者,只需在创建或保留观察者后添加观察者,并在释放观察者之前删除观察者。

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

【讨论】:

用例:您想在 viewDidUnload 和 dealloc 中删除观察者。这将删除它们两次,如果您的 viewController 从内存警告中卸载,然后也被释放,则会引发异常。您建议如何处理这种情况? @bandejapaisa:和我在回答中所说的差不多:跟踪我是否在观察,只有在观察时才尝试停止观察。 不,这不是一个有趣的问题。您不必跟踪这一点;您应该能够简单地取消注册 dealloc 中的所有侦听器,而无需关心您是否碰巧点击了添加它的代码路径。它应该像 NSNotificationCenter 的 removeObserver 一样工作,它并不关心你是否真的有一个。此异常只是创建了原本不存在的错误,这是糟糕的 API 设计。 @GlennMaynard:就像我在回答中所说的那样,“如果你在观察者不知情的情况下切断了它的通知,预计事情会中断;更具体地说,期望观察者的状态变得陈旧,因为它没有从以前观察到的对象接收更新。”每个观察者都应该结束自己的观察;理想情况下,未能做到这一点应该是高度可见的。 问题中没有任何内容涉及删除 other 代码的观察者。【参考方案2】:

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

【讨论】:

你最终到处都是 BOOL,最好还是创建一个 KVO 包装器对象来处理添加和删除观察者。它可以确保您的观察者只被删除一次。我们已经使用了这样的对象,并且它可以工作。 如果您不总是在观察的话,这是个好主意。【参考方案3】:

观察者模式的重点是允许一个被观察的类被“密封”——不知道也不关心它是否被观察。您明确地试图打破这种模式。

为什么?

你遇到的问题是你假设你被观察了,而实际上你并没有。这个物体没有开始观察。如果你想让你的班级控制这个过程,那么你应该考虑使用通知中心。这样,您的班级就可以完全控制何时可以观察到数据。因此,它不关心谁在看。

【讨论】:

他问的是listener如何知道它是否在听东西,而不是被观察的对象如何知道它是否被观察。【参考方案4】:

在你的 removeObserver 调用周围尝试一下

@try
   [someObject removeObserver:someObserver forKeyPath:somePath];
@catch(id anException)
   //do nothing, obviously it wasn't attached because an exception was thrown

【讨论】:

1+ 好答案,对我有用,我同意您在编辑之前的咆哮。 赞成删除我很可能同意的咆哮。 这里没有其他优雅的解决方案吗?这个每次使用至少需要 2 毫秒......想象一下它在 tableviewcell 中 投了反对票,因为您没有说这对生产代码不安全,并且随时可能失败。通过框架代码引发异常在 Cocoa 中不是一个选项。 如何在 swift 2.1 中使用此代码。 do try self.playerItem?.removeObserver(self, forKeyPath: "status") catch let error as NSError print(error.localizedDescription) 得到警告。【参考方案5】:

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

【讨论】:

你知道我如何检索特定的观察者吗? objectAtIndex: 没有产生想要的结果。) @MattDiPasquale 你知道我如何阅读代码中的观察信息吗?在印刷品中它很好,但它是一个指向无效的指针。我应该怎么读? observationInfo 是 Xcode 调试论文中记录的调试方法(标题中带有“魔法”的东西)。你可以试着查一下。我可以告诉你,如果你需要知道是否有人在观察你的对象——你做错了什么。重新思考你的架构和逻辑。学得很辛苦。) 来源:NSKeyValueObserving.h 加 1 表示可笑的死胡同,但仍然有些帮助【参考方案6】:

当您将观察者添加到对象时,您可以将其添加到 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];
    

【讨论】:

如果这种情况发生在多线程世界中,你需要确保你的数组是线程安全的 您保留了一个对象的强引用,这会在每次将对象添加到列表中时增加保留计数,并且除非从数组中删除其引用,否则不会被释放。我更喜欢使用NSHashTable/NSMapTable 来保留弱引用。【参考方案7】:

在我看来 - 这类似于 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:@""];

你需要一些特定的检查 - 实现你自己的类来处理一组观察者并将它用于你的检查。

【讨论】:

[self removeObserver:nil forKeyPath:@""];需要先去:[super viewWillDisappear:animated]; @JoshuaHart 为什么? 因为它是一种拆卸方法(dealloc)。当您覆盖某种拆卸方法时,您最后调用 super。喜欢:- (void) setupSomething [super setupSomething]; … - (void) tearDownSomething … [super tearDownSomething]; viewWillDisapear 不是拆卸方法,它与 dealloc 没有任何关系。如果您向前推进到导航堆栈,将调用 viewWillDisapear,但您的视图将保留在内存中。我知道您使用设置/拆卸的逻辑要去哪里,但是在这里这样做不会带来任何实际好处。只有当您在基类中有一些逻辑时,您才会希望在 super 之前放置删除,这可能与当前观察者冲突。【参考方案8】:

我不是那种 try 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

【讨论】:

【参考方案9】:

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

if ([tableMessage observationInfo] == nil)

   NSLog(@"add your observer");

else

  NSLog(@"remove your observer");


【讨论】:

根据 Apple 文档:observationInfo 返回一个指针,该指针标识所有向接收者注册的观察者的信息。 这在@mattdipasquale's answer中说得更好【参考方案10】:

除了亚当的回答,我想建议使用这样的宏

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

使用示例

- (void)dealloc 
    SafeRemoveObserver(someObject, self, somePath);

【讨论】:

抛出异常有多疯狂?如果什么都没有,为什么它不做任何事情?

以上是关于我如何判断一个对象是不是附加了键值观察器的主要内容,如果未能解决你的问题,请参考以下文章

JS判断两个对象内容是不是相等

[深入浅出Cocoa]详解键值观察(KVO)及其实现机理

R语言data.table导入数据实战:data.table设置键值(key)复合键设置删除键设置键值之后的数据连接(join)更加方便设置了键值之后可以使用keyby语法代替by语法

如何解决 Realm 对象观察器的泛型问题?

AVQueuePlayer 的键值观察器

Swift 中是不是提供键值观察 (KVO)?