【问题标题】:Using Key Value Observing to detect when an object gets deallocated使用 Key Value Observing 检测对象何时被释放
【发布时间】:2013-08-05 17:58:45
【问题描述】:

我如何知道一个对象何时被释放?我正在监听 kvo 更改,但在保留计数变为 0 之前对象 get 已被释放,并且我收到以下警告:

类 MyViewController 的实例 0x16562be0 已被释放,而键值观察器仍向其注册。观察信息被泄露,甚至可能被错误地附加到其他对象上。在 NSKVODeallocateBreak 上设置断点以在调试器中停止。以下是当前观察信息:

基本上我要做的是检测模型何时被解除。 我不能使用 Delegate,因为呈现的 viewController 是动态的,而我的 mainViewController 除了它们是 UIViewController 的子类之外,对它们一无所知。

[anotherViewController addObserver:self forKeyPath:@"retainCount" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // Here check for the changes and see of the new value is 0 or not
}

我也尝试监听 viewController 的 superView 被更改为 nil

[anotherViewController.view addObserver:self forKeyPath:@"superView" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:nil];

【问题讨论】:

  • 为了更具体地了解您的情况,忽略所有其他应该从不使用 retainCount 的充分理由,KVO 仅适用于类符合 KVO 的属性。我以前从未有过关心的理由,但我敢打赌,retainCount 不符合 KVO。您需要采取完全不同的方法来应对视图控制器被解除(或者它是否被解除分配?它们不是同一件事......)。
  • @AndrewMadsen:从字面上看,对于retainCount,类字面上不能符合KVO,因为他在这里寻找的是,因为它永远不会是0不是零。
  • @aryaxt:你应该问一个单独的问题。
  • 如果主视图控制器知道的唯一是其他视图控制器是UIViewController的实例,为什么它需要使用KVO或以其他方式知道它们何时被解雇了?相反,如果主视图控制器需要知道另一个视图控制器已被解除,那么它需要知道的不仅仅是视图控制器是视图控制器。

标签: objective-c key-value-observing


【解决方案1】:

您只能对对象支持的键进行键值观察。你想在这里做的事情根本不可能——一个对象的观察者应该在它到达dealloc时都已经消失了。您将需要构建您的应用程序,以便该对象在需要时一直保留,或者在它消失之前主动告知相关方。

查看对象的retainCount 绝不是一个好主意。只要它有用,它只对调试有用——即便如此,还有更好、更可靠的工具。 retainCount 的结果只是具有误导性,它并没有像大多数人期望的那样工作。等待它为 0 是徒劳的,因为保留计数为 0 的对象不能存在 - 当保留计数为 1 的对象被释放时,它会被释放,然后你就不能再向它发送消息了. (事实上​​,框架实际上无法表示 0 保留计数,因为它是一个无法访问的状态。)

【讨论】:

    【解决方案2】:

    更新

    截至 2017 年底(iOS 11、macOS 10.13),当一个对象被释放时,它会自动取消注册任何剩余的观察者。来自Foundation release notes for that year

    宽松的键值观察注销要求

    在 10.13 之前,如果有任何观察者,KVO 会抛出异常 在自动通知对象的 -dealloc 完成后仍然注册 跑步。此外,如果所有观察者都被移除,但有些被移除 在 dealloc 期间从另一个线程中删除,异常会 错误地仍然被抛出。该要求已在 10.13,受两个条件限制:

    • 对象必须使用 KVO 自动通知,而不是手动 调用 -will 和 -didChangeValueForKey: (即它不应该返回 NO 来自 +automaticallyNotifiesObserversForKey:)
    • 对象不得 覆盖内部 KVO 状态的(私有)访问器

    如果所有这些都为真,则 -dealloc 之后的任何剩余观察者 退货将由 KVO 清理;这也有点多 比重复调用 -removeObserver 方法更有效。

    截至 2020 年底(iOS 14、macOS 10.16),KVO is even more careful 当对象在释放期间仍有观察者时:

    键值观察

    iOS 和 iPadOS 14 beta 5 中的新功能

    • Key-Value Observation 移除工具现在采用确定性 记账方法。难以诊断的病例 崩溃,尤其是那些 KVO 发出访问问题的信号 解除分配的观察者指针或与不正确相关的观察者 对象,现在产生一个异常,指出哪个观察到的对象 需要一个未接的removeObserver(_:) 电话,以及由哪个观察员。这 当 KVO 可以检测到时,异常之前被抛出为“尽力而为” 问题;新的确定性簿记允许它被抛出 适用于所有需要removeObserver(_:) 的情况。

      改进的确定性还允许改进 Swift API 处理。 NSKeyValueObservation 的实例,由 Swift 生成 NSObject.observe(_:changeHandler:) 方法,利用 与此簿记集成,因此它们现在自动失效 当被观察物体被释放时,不管物体如何 实现其 KVO 行为。这适用于该 API 的所有使用 macOS 11 Big Sur 测试版,包括使用先前版本构建的进程 版本的 SDK,并消除了某些类别的崩溃, 有时需要使用旧版 API。 (65051563)

    原创

    这里有一些问题。

    一个问题是你问错了问题。你的意思是问“我如何在正确的时间取消注册我的观察者,在目标被释放之前?”相反,您提到了retainCount,这往往会激怒人们指责您使用retainCount,而不是帮助您做您想做的事情,即在正确的时间注销您的观察者。

    另一个问题是您的视图控制器不拥有它的模型(这意味着它没有对模型的强引用)。通常你希望你的视图控制器拥有它的模型,以防止这种问题。当你的视图控制器存在时,它需要一个模型来操作,所以它应该拥有这个模型。当视图控制器被释放时,它应该停止观察它的模型并释放它。 (如果您使用的是 ARC,它会在 dealloc 末尾自动释放模型)。如果您的视图控制器反复打开和关闭屏幕,您也可以选择在 viewWillDisappear: 方法中取消注册。

    请注意,一个对象可以同时由多个其他对象拥有。如果您有多个视图控制器在同一个模型上运行,则它们都应该拥有该模型,这意味着它们都应该对模型具有强引用。

    第三个问题是您(可能)直接使用 KVO。内置的 KVO API 使用起来不是很愉快。看看MAKVONotificationCenter。当观察者或目标被释放时,这个 KVO 包装器会自动取消注册观察者。

    【讨论】:

    • MAKVONotificationCenter 很方便,但是将观察者的注销绑定为dealloc 的自动行为可能会导致非常奇怪且难以修复的错误。在释放任何给定对象期间,对象图可能不一致。应在对象图收割之前删除更改观察。
    • 我很想看到这样一个错误的例子。
    • 经常出现在 Xcode 和其他复杂的应用程序中;真正的杀手是对象图拆卸中的顺序依赖关系,但是在对象图拆卸期间被触发的 KVO 也出现了。通过转向非常严格的“先失效后销毁”模型,错误数量大大减少。触发器通常是一个被调用的 setter,它会触发一个观察者,然后尝试处理现在断开的对象图。
    【解决方案3】:

    如果您有兴趣在对象被解除分配时收到通知,您可以在 dealloc 中发送通知,但不要引用被解除分配的对象。

    例如

    [[NSNotificationCenter defaultCenter] postNotificationName:@"myclass_dealloced" \
                                          object:[NSValue valueWithPointer:self]];
    

    但您永远都不想取消引用该指针...

    仅用于调试和测试。

    【讨论】:

    • 我想过,但我不喜欢这个解决方案。我希望它是自动化的。
    • 在我制作的测试框架中,我调走了 alloc / copy / dealloc 并使其自动发生......我可能无法发布该代码,但我使用了 JRSwizzle(我认为这是它叫什么)非常轻松地进行调动,然后我发送分布式通知并有另一个进程与 UI 监视事件。
    • 我真的很喜欢method swizzling,除了,我从来没有找到拦截方法,然后调用原来的方法。知道这是否可能吗?
    • 绝对你将原始方法转换为一个名为 originalDealloc 的方法,然后在你的 dealloc 中调用它......或者由于 dealloc 足够简单,你通常可以重新实现它。
    • 非常酷,我正在尝试编写一个 NSObject 类别来执行此操作。仍然没有工作,但我想我已经接近了。 github.com/aryaxt/OCMethodInterceptor
    【解决方案4】:

    dealloc 期间尝试自动注销观察者为时已晚

    dealloc被调用时,对象图的状态是未定义的。具体来说,通常无法保证解除分配的顺序,并且可能经常根据异步进程和/或autorelease 而改变。

    虽然解除分配对象强烈引用的图应该是一致的,但随着对象被解除分配,这种情况会迅速改变。

    对于被释放对象的观察者也是如此;随着对象图的释放,观察到的对象状态可能会发生变化。随着它的变化,它可能会导致观察者在对象图处于不一致、被释放的状态时触发。

    您确实需要将释放与观察逻辑具体分开。

    也就是说,当你的控制器从屏幕上消失时,它应该主动关闭模型层,包括拆除任何观察者(或通知任何观察者模型层即将消失)。

    【讨论】:

      【解决方案5】:

      您的观察者需要在他们放开对象的同时注销他们的通知。

      例如,如果您的对象在其属性之一上注册通知,请在属性更改或设置为 nil 之前取消注册所有通知。

      永远不应该对已经完全丢失的对象“挂起”通知注册。如果您忘记了对象,如何取消注册通知?

      【讨论】:

        【解决方案6】:

        按照 KVO 所说的去做。观察,采取相应行动,并在需要时手动发出钥匙信号。这样,您当然可以知道对象何时被释放。 当您从 Object 中 removeObserver 并且它已经被释放时,方法调用将作用于 nil ,这不会造成任何伤害,或者您的观察对象仍然持有引用,在这种情况下您仍然可以采取相应的行动。

        使用 ARC,这不是问题,也是一大好处。

        自己测试一下..

        // public header.
        @interface ObjectToBeObserved : NSObject
        @end
        
        // declare in private header
        // because you dont want to allow triggering from outside
        @interface ObjectToBeObserved : NSObject
        // use some artificial property to make it easy signalling manually.
        @property (nonatomic) BOOL willDealloc;
        @end
        
        @implementation ObjectToBeObserved 
        -(void)dealloc {
            [self willChangeValueForKey:@"willDealloc"];
            [self didChangeValueForKey:@"willDealloc"];
        }
        @end
        

        在您的 Observer 端,您只需执行经典的 KVO 设计模式..

        void* objectDeallocatedContext = & objectDeallocatedContext;
        @implementation ObservingObject {
            // easy to see you could even make a protocol out of the design pattern
            // that way you could guarantee your delegate has such property to observe
            __weak ObjectToBeObserved *delegate;
        }
        -(instancetype)initWithObservableDelegate:(ObjectToBeObserved*)observable {
            if (!(self=[super init])) return nil;
            delegate = observable;
            // see i use observe old value here..
            if (delegate!=nil)
                [delegate addObserver:self forKeyPath:@"willDealloc" options:(NSKeyValueObservingOptionOld) context:objectDeallocatedContext];
            return self;
        }
        -(void)dealloc {
            if (delegate!=nil)
                [delegate removeObserver:self forKeyPath:@"willDealloc" context: objectDeallocatedContext];
        }
        -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
            if (context==objectDeallocatedContext) {
                NSLog(@"the observed object deallocated");
                // in theory you hold still a weak reference here 
                // which should be nil after this KVO signal arrived.
                // the object in the signal therefore might not be valid anymore,
                // which is what you want when observing deallocation.
            }
        }
        @end
        

        KVO 是一种信号模式,而不是一种知道信号对象是否仍然有效的方法。但是当物体消失时它不会发出任何信号,那些当你能收到信号时你就很好了。因为我选择使用void* context 观看NSKeyValueObservingOptionOld 值,所以它甚至会在设置对象人工“willDealloc”属性之前发出信号(嗯,甚至没有设置)。 KVO 可以在没有有效对象的情况下到达,但仍有要比较的上下文。你只需要ping

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-11-09
          • 2011-05-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多