【问题标题】: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 解决方案并发表评论以进一步调查原因。
  • 我只是在处理类似的事情,我发现我需要更深入地研究我的设计并对其进行调整,这样我就不需要再次移除观察者了。
  • 使用这个答案中建议的布尔值对我来说效果最好:stackoverflow.com/a/37641685/4833705

标签: objective-c cocoa key key-value-observing


【解决方案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);
    }
    

    【讨论】:

    • 抛出异常有多疯狂?如果什么都没有,为什么它不做任何事情?
    猜你喜欢
    • 2012-12-03
    • 1970-01-01
    • 2016-01-31
    • 1970-01-01
    • 2015-03-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-06
    相关资源
    最近更新 更多