1

我怎样才能知道一个对象何时被释放?我正在监听 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];
4

6 回答 6

8

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

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

于 2013-08-05T18:14:30.600 回答
4

更新

截至 2017 年底(iOS 11、macOS 10.13),当一个对象被释放时,它会自动取消注册任何剩余的观察者。来自当年的基金会发行说明

宽松的键值观察注销要求

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

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

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

并且截至 2020 年底(iOS 14、macOS 10.16),当对象在释放期间仍有观察者时, KVO 会更加小心:

键值观察

iOS 和 iPadOS 14 beta 5 中的新功能

  • Key-Value Observation 移除工具现在采用确定性簿记方法。会产生难以诊断的崩溃的情况,尤其是那些 KVO 表示访问已释放的观察者指针或与不正确对象关联的观察者时出现问题的情况,现在会产生一个异常,指出哪个观察到的对象需要未接removeObserver(_:)呼叫,以及哪些观察者需要一个未接来电。当 KVO 可以检测到问题时,此异常以前被作为“尽力而为”抛出;新的确定性簿记允许在需要的所有情况下抛出它removeObserver(_:)

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

原来的

这里有几个问题。

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

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

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

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

于 2013-08-05T18:37:11.923 回答
2

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

例如

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

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

仅将其用于调试和测试。

于 2013-08-05T18:27:01.253 回答
1

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

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

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

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

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

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

于 2013-08-06T15:30:53.563 回答
0

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

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

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

于 2013-08-05T18:35:46.377 回答
0

照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

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

于 2022-02-21T03:37:34.117 回答