Cocoa bindings and KVO, unregister the observer when the observer receives `dealloced`

How can I unregister an observer when an observer gets dealloced ?

How do cocoa bindings handle a situation where observable objects are freed?

Using manual KVO, I need to remove the observer (removeObserver) before the dealloc object ... how do cocoa bindings (stop observing the dealloc of the observed object)?

+7
cocoa cocoa-bindings key-value-observing dealloc
source share
3 answers

Update 2017

As GGregBrown noted in a comment, the original answer of 2013 does not work in 2017. I assume that the original answer really worked in 2013, since my practice is not to answer without testing, but I no longer have the code I used.

So how do you solve this in 2017? The easiest answer is swizzling, and some of them will find a contradiction, but it is not necessary to use blocks. Below is a quick proof of concept with the following reservations:

  • It is not thread safe. Think about what might happen if two or more threads execute code at the same time. Standard methods will solve this.

  • Efficiency has not been taken into account! For example, you can run swizzle dealloc once for each class and save the observer / key path list in the instance-related object.

  • This code only supports automatic deletion; you cannot manually delete an observer. You can 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 intended to eliminate the effects of ARC. In particular, methods usually retain their self argument, dealloc methods do not, and blocks follow the same persistence model as needed. To use a block as an implementation of dealloc , this behavior must be overridden, for which __unsafe_unretained used.

To use the code above, you simply replace:

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

from:

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

Given the above warnings, the above code will do the job in 2017 (no warranty for future years!)

Original answer 2013

Here, in general terms, you can handle this and similar situations.

First find related objects. In short, you can attach related objects to any other object (using objc_setAssociatedObject ) and indicate that the related object should be kept as long as the object to which it is attached is around (using OBJC_ASSOCIATION_RETAIN ).

Using related objects, you can arrange for automatic removal of the observer when the observed object is released. Let X be an observer, and Y be observable objects.

Create an “unregistered” class, say Z, which accepts (via init) X and Y and in its dealloc method does removeObserver .

To set up surveillance, X:

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

Now that Y is freed, Z will be freed, which will cause Z dealloc be called and unregister X.

If you need to remove the observation X while Y is still active, you do this by deleting the associated object - and this will result in its dealloc ...

You can use this template whenever you want to call something when another object is freed.

NTN

+5
source share

From the KVO manual (emphasis mine): the method of observing the value of a key value does not support strong references to the object of observation, observed objects or context. You must ensure that you maintain strong references to observational and observable objects and context when necessary .

Cocoa Bindings control strong links during bind: and unbind:

To answer your question, “How can I unregister the observer when the object is released?”: You do not unregister the observer because the observer must maintain a strong link to the observed object. Thus, the observed object is not freed.

Another way to think about it is to observe properties, but not the observed object itself. Therefore, you can only when the properties are set to nil. Because you maintain a strong reference as an observer, the observed object will not be released until the observed object is respected.

So, if the observed object was deleted as a property that you are observing another object, this will cause you to stop observing it (release a strong link), and then allow the observed object to be freed. This property that you are observing may be a collection of objects, so you must observe the deleted object, forcing you to stop observing and let the dealloc object.

+1
source share

My patter I tried and look at work: X wants to watch Y

1) In Y declare a property

 @property (assign) id dealloced; 

2) In Y dealloc

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

3) In X, also observe Y keyPath dealloced

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

4) In X, simply handle the KVO change to free and unregister all cases

 - (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 contain a link to Y

0
source share

All Articles