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