Question

How can i unregister the observer, when the observing object gets dealloced?

How can cocoa bindings handle a situation when the observed objects gets deallocated?

By using manual KVO, i have to remove the observing (removeObserver) before dealloc the object... how does Cocoa bindings handle this (stop observing on dealloc of the observed object)?

Was it helpful?

Solution

Update 2017

As @GregBrown has pointed out in the comments the original 2013 answer does not work in 2017. I assume the original answer did work in 2013, as my practice is not to answer without testing, but I no longer have any code I used.

So how do you do solve this in 2017? The simplest answer is swizzling, which some will find a contradiction but it need not be when using blocks. Below is a quick proof-of-concept with the following caveats:

  • This is not thread safe. Consider what might happen if two or more threads execute the code at the same time. Standard techniques will address that.

  • Efficiency was not a consideration! For example, you might wish to swizzle dealloc once per class and keep a list of observer/keypath in a per-instance associated object.

  • This code only supports auto-removal, you cannot manually choose to remove an observer. You might wish to change that.

The code:

@implementation AutoRemovedKVO

typedef void (*DeallocImp)(id, SEL);

+ (void)forTarget:(NSObject *)target
      addObserver:(NSObject *)observer
       forKeyPath:(NSString *)keyPath
          options:(NSKeyValueObservingOptions)options
          context:(nullable void *)context
{
   // register the observer
   [target addObserver:observer forKeyPath:keyPath options:options context:context];

   // swizzle dealloc to remove it

   Class targetClass = target.class;
   SEL deallocSelector = NSSelectorFromString(@"dealloc");
   DeallocImp currentDealloc = (DeallocImp)method_getImplementation(  class_getInstanceMethod(targetClass, deallocSelector) );

   // don't capture target strongly in block or dealloc will never get called!
   __unsafe_unretained NSObject *targetPointer = target;

   void (^replacementBlock)(id self) = ^(__unsafe_unretained id self)
   {
      if (self == targetPointer)
         [targetPointer removeObserver:observer forKeyPath:keyPath];

      currentDealloc(self, deallocSelector);
   };

   class_replaceMethod(targetClass, deallocSelector, imp_implementationWithBlock(replacementBlock), "v@:");
}

@end

Both uses of __unsafe_unretained are to work around consequences of ARC. In particular methods usually retain their self argument, dealloc methods do not, and blocks follow the same retain-as-needed model. To use a block as the implementation of dealloc this behaviour needs to be overridden, which is what __unsafe_unretained is being used for.

To use the above code you simply replace:

[b addObserver:a forKeyPath:keyPath options:options context:NULL];

with:

[AutoRemovedKVO forTarget:b addObserver:a forKeyPath:keyPath options:options context:NULL]; 

Allowing for the above caveats the above code will do the job in 2017 (no guarantee for future years!)

Original 2013 Answer

Here in outline is how you can handle this, and similar situations.

First look up associated objects. In brief you can attach associated objects to any other object (using objc_setAssociatedObject) and specify that the associated object should be retained as long as the object it is attached to is around (using OBJC_ASSOCIATION_RETAIN).

Using associated objects you can arrange for an observer to be automatically removed when the observed object is deallocated. Let X be the observer and Y be the observed objects.

Create an "unregister" class, say Z, which takes (via init) an X & Y and in its dealloc method does removeObserver.

To setup the observation, X:

  1. Creates an instance of Z, passing itself and Y.
  2. Registers itself as an observer of Y.
  3. Associates Z with Y.

Now when Y is deallocated Z will be deallocated, which will result in Z's dealloc being called and unregistering the observation of X.

If you need to remove the observation of X while Y is still active you do this by removing the associated object - and doing so will trigger its dealloc...

You can use this pattern whenever you want to trigger something when another object is deallocated.

HTH

OTHER TIPS

From the KVO guide (emphasis mine): The key-value observing addObserver:forKeyPath:options:context: method does not maintain strong references to the observing object, the observed objects, or the context. You should ensure that you maintain strong references to the observing, and observed, objects, and the context as necessary.

Cocoa Bindings manage the strong references during bind: and unbind:.

To answer your question "How can i unregister the observer, when the observing object gets dealloced?": You don't unregister the observer, because the observer should maintain a strong reference to the observed object. Thus the observed object doesn't get deallocated.

Another way to think of this, you are observing properties, but not the observed object itself. So you only care when properties are set to nil. Because you are maintaining a strong reference as an observer, the observed object will not be deallocated until the observing object is done observing it.

So if the observed object was removed as a property you are observing of another object, that would cause you to stop observing it (releasing the strong reference) and then allow the observed object to be deallocated. That property you are observing may be a collection of objects, so you would observe the object being removed, causing you to stop observing and allowing the object to dealloc.

My patter that i tried and looks to work : X wants to observe Y

1) In Y declare a property

@property (assign) id dealloced;

2) In Y's dealloc

- (void)dealloc {
   self.dealloced = self;
}

3) In X observe also Y's keyPath dealloced

[Y addObserver:self forKeyPath:@"dealloced" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];

4) In X just handle the KVO change for dealloced and unregister all observing

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    ...
    if ([keyPath isEqual:@"dealloced"]) {
       id obj = change[@"new"];
       [obj removeObserver:self forKeyPath:@"dealloced"];
       // remove other observing as well
    }
    ...
}

And in X we don't even need to hold a reference to Y

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top