aryaxt
aryaxt

Reputation: 77596

Using Key Value Observing to detect when an object gets deallocated

How can I find out when an object is being released? I am listening for kvo changes, but the object get's deallocated before the retain count goes to 0, and I get the following warning:

An instance 0x16562be0 of class MyViewController was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:

Basically what I'm trying to do is to detect when the model is dismissed. I can't use a Delegate, because the viewControllers being presented are dynamic, and my mainViewController has no knowledge about them other than the fact that they are subclasses of 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
}

I also tried listening for the superView of the viewController being changed to nil

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

Upvotes: 3

Views: 4032

Answers (6)

Ol Sen
Ol Sen

Reputation: 3368

just do what KVO says. Observe, act accordingly and signal the Key manually when you need to. That way you can of course know when an object gets deallocated. When you removeObserver from the Object and it is already deallocated then the method call is acting on nil which does no harm or your observing object holds still a reference and in such case you can still act accordingly.

With ARC this is not a problem and one of the great benefits.

Test it yourself..

// 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

In your Observer side you just do classic KVO design pattern..

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 is a signal pattern, not a way to know if a signalling object is still valid. But when the object is gone it will not signal anything, those when you can receive the signal you are just fine. Because i choose to watch the NSKeyValueObservingOptionOld value with a void* context, it gets even signaled before the objects artificial "willDealloc" property is set (well, not even set). The KVO can arrive without a valid object but has still a context to compare to. You just need the ping

Upvotes: 0

rob mayoff
rob mayoff

Reputation: 385590

UPDATE

As of late 2017 (iOS 11, macOS 10.13), when an object is deallocated, it automatically unregisters any remaining observers. From the Foundation release notes for that year:

Relaxed Key-Value Observing Unregistration Requirements

Prior to 10.13, KVO would throw an exception if any observers were still registered after an autonotifying object's -dealloc finished running. Additionally, if all observers were removed, but some were removed from another thread during dealloc, the exception would incorrectly still be thrown. This requirement has been relaxed in 10.13, subject to two conditions:

  • The object must be using KVO autonotifying, rather than manually calling -will and -didChangeValueForKey: (i.e. it should not return NO from +automaticallyNotifiesObserversForKey:)
  • The object must not override the (private) accessors for internal KVO state

If all of these are true, any remaining observers after -dealloc returns will be cleaned up by KVO; this is also somewhat more efficient than repeatedly calling -removeObserver methods.

And as of late 2020 (iOS 14, macOS 10.16), KVO is even more careful when an object still has observers during deallocation:

Key-Value Observing

New Features in iOS & iPadOS 14 beta 5

  • Key-Value Observation removal facilities now employ deterministic bookkeeping methods. Cases that would have produced hard-to-diagnose crashes, especially those where KVO signals problems accessing deallocated observer pointers or observers associated with incorrect objects, now produce an exception pinpointing which observed object needs a missed removeObserver(_:) call, and by which observers. This exception was previously thrown as ‘best effort’ when KVO could detect the problem; the new deterministic bookkeeping allows it to be thrown for all cases where removeObserver(_:) is needed.

    The improved determinism also allows improved Swift API handling. Instances of NSKeyValueObservation, produced by the Swift NSObject.observe(_:changeHandler:) method, take advantage of integration with this bookkeeping so they now invalidate automatically when the observed object is released, regardless of how the object implements its KVO behavior. This applies to all usage of this API in macOS 11 Big Sur beta, including on processes built with previous versions of the SDK, and eliminates certain classes of crashes that sometimes required using the legacy API instead. (65051563)

ORIGINAL

There are a few problems here.

One problem is that you asked the wrong question. You meant to ask “How do I deregister my observer at the right time, before the target is deallocated?” Instead, you mentioned retainCount, which tends to provoke people into berating you about using retainCount instead of helping you do what you're trying to do, which is deregister your observer at the right time.

Another problem is that your view controller doesn't own its model (meaning it doesn't have a strong reference to the model). Usually you want your view controller to own its model, to prevent exactly this sort of problem. While your view controller exists, it needs a model to operate on, so it should own the model. When the view controller is being deallocated, it should stop observing its model and release it. (If you're using ARC, it will release the model automatically at the end of dealloc). You might also choose to deregister in your viewWillDisappear: method, if your view controller goes on and off of the screen repeatedly.

Note that an object can be owned by multiple other objects simultaneously. If you have several view controllers operating on the same model, they should all own the model, meaning that they should all have strong references to the model.

A third problem is that you're (probably) using KVO directly. The built-in KVO API is not very pleasant to use. Take a look at MAKVONotificationCenter. This KVO wrapper automatically unregisters an observer when the observer or the target is deallocated.

Upvotes: 7

bbum
bbum

Reputation: 162712

Trying to automatically de-register observers during dealloc is too late.

When dealloc is called, the state of the object graph is undefined. Specifically, order of deallocation is typically not guaranteed and may often change in light of asynchronous processes and/or autorelease.

While the graph the deallocating object strongly references should be coherent, that'll quickly change as the object is deallocated.

The same holds true for the observer of the object being deallocated; as deallocation of an object graph happens, the observed objects state may likely change. As it changes, it may cause observers to fire while the object graph is in the inconsistent, being deallocateed, state.

You really need to concretely separate deallocation from observation logic.

That is, when your controller is dismissed from screen, it should actively dismiss the model layer, including tearing down any observers (or notifying any observers that the model layer is about to go away).

Upvotes: 1

Colin Cornaby
Colin Cornaby

Reputation: 746

Your observers need to de-register their notifications at the same time they let go of the object.

For example, if your objects are registering notifications on one of their properties, de-register all the notifications before the property is changed or set to nil.

There never should be "hanging" notification registrations to objects that have been simply lost track of. How can you deregister your notifications if you lose track of the object?

Upvotes: 0

Grady Player
Grady Player

Reputation: 14549

if you are interested in getting notified when an object gets deallocated you could send a notification in dealloc, but don't reference the object getting dealloc'ed.

for instance

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

but you wouldn't ever want to dereference that pointer...

use this only for debugging and testing.

Upvotes: 2

Chuck
Chuck

Reputation: 237030

You can only do Key-Value Observing on keys for which the object supports it. What you want to do here is simply not possible — an object's observers are all supposed to be gone by the time it gets to dealloc. You will need to structure your application such that either this object is kept around as long as it is needed or it actively tells interested parties before it goes away.

And looking at an object's retainCount is just never a good idea. As far as it is useful, it is only useful for debugging — and even then there are much better and more reliable tools. The result of retainCount is simply misleading, and it does not work the way most people expect. Watching for it to be 0 is an exercise in futility, because no object can exist with a retain count of 0 — when an object with a retain count of 1 is released, it gets deallocated, and then you are not allowed to message it anymore. (In fact, the framework literally has no way of representing a 0 retain count because it's an unreachable state.)

Upvotes: 9

Related Questions