Cocoa bindings and KVO, unregister the observer, when the observing object gets `dealloced`

cocoa, cocoa-bindings, dealloc, key-value-observing

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:

- Creates an instance of Z, passing itself and Y.

- Registers itself as an observer of Y.

- 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

Problem

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)?

Original source