How do you remove KVO from a weak property? - objective-c

I have a view (we'll call this view A) that has a weak property to its superview (view B). View A KVO's its superview, view B. Since view A's reference to view B is a weak property (to prevent a retain cycle), how can I remove the observer (A observing B)? View A's reference to view B gets nil'd out before I have a chance to remove it.
A outlives B since the view controller has a strong reference to A. Here's the leaking log message:
An instance 0x9ac5200 of class UITableView 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:
<NSKeyValueObservationInfo 0x8660360> (
<NSKeyValueObservance 0x8660320: Observer: 0x8660020, Key path: contentOffset, Options: <New: YES, Old: NO, Prior: NO> Context: 0x8660020, Property: 0x864ac80>
)
B is a UITableView. Setting a breakpoint at NSKVODeallocateBreak yields useless results.
In A's removeFromSuperview, I try to remove the observer but A's reference to B is already nil.
Switching to unsafe_unretained and do things more manually or calling [A removeFromSuperview] in the view controller's dealloc solves the problem. I'd like to know how to solve this using a weak property though.
Here's the relevant code: https://gist.github.com/2822776

I find any kind of code required specially for this case really unnecessary as removal can be automated.
With the introduction of ARC, Apple should have provide automatic removal of observers that would fix cases like this, but unfortunately they didn't. But I've made my own category that adds this lacking feature: https://github.com/krzysztofzablocki/SFObservers
I've explained how I did manage that on my blog: http://www.merowing.info/2012/03/automatic-removal-of-nsnotificationcenter-or-kvo-observers/
If you look at my solution you will notice, that it makes sure that original code is called, even if one of the methods call other ones, so that even if apple changes its internal behavior the category will still work fine :)

You could define an explicit weak property referencing the superview and then observe self with a key path like #"propertyReferringSuperview.propertyOfSuperview"? When you get a KVO notification, you check if self.propertyReferringSuperview == nil and stop observing #"propertyReferringSuperview.propertyOfSuperview".

Instead of adding a weak property, you could just use the superview property and implement willMoveToSuperview: to add/remove the KVO observation.
- (void)willMoveToSuperview:(UIView *)newSuperview {
[self.superview removeObserver:self forKeyPath:#"contentOffset" context:context];
[newSuperview addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:context];
[super willMoveToSuperview:newSuperview]; // optional as default implementation does nothing
}

Related

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];
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.)
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.
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.
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).
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?
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

NSArrayController - Observing selectionIndex

When I try setting up observation of this specific key, nothing happens. Here is what I mean.
I have a standard Cocoa application, with an NSTableView, and I figured out how to change the image shown in an image view based on what cell was selected.
Now, I am trying to figure out how to disable/enable buttons by the selected index. What I mean by this, is that I have a button in the window, that is disabled on certain indexes.
SomeClass's init method
-(id)init {
if (self=[super init]) {
[arrayController addObserver:self forKeyPath:#"selectionIndex" options:NSKeyValueObservingOptionNew context:NULL];
}
}
However, when I implement the observeValueForKeyPath: method, the changes are not being picked up as I pick new indexes. As a matter of fact, the outlet shows (null) if I try logging it. However, when I add the observer in AppDelegate, AppDelegate (when specified as the observer) picks up changes.
Is there some reason my generic SomeClass object does not? Should it be done a different way?
NOTE:
I tried subclassing SomeClass as a NSWindow, then making the window's owner SomeClass, and setting up the observer in awakeFromNib, and this works, but seems like a bad way to do it.
The init method happens too early in the process, before the outlets are connected (I think). Putting the code in awakeFromNib will work correctly. This is from Apple's docs in the NSObject class reference:
The nib-loading infrastructure sends an awakeFromNib
message to each object recreated from a nib archive, but only after
all the objects in the archive have been loaded and initialized. When
an object receives an awakeFromNib message, it is guaranteed to have
all its outlet and action connections already established.

Keeping pointers to subviews

Subviews added to a view are automatically retained by the view. Suppose you want to have a separate pointer to the same subview so you don't need to constantly retrieve it via its tag.
What type of #property is necessary for such a case? I assume that setting the property to retain is not a good idea since the main view is already retaining it? Should it be assign?
Or, is using #property entirely unnecessary here unless you plan to re-assign it later or refer to it with dot notation?
You can use either retain or assign.
Of course, if you use retain, you have to set the property to nil or release its object in viewDidUnload and dealloc.
The reason some people prefer retain is because it means the property is still valid in viewDidUnload. So if you have other cleanup to do, and that cleanup requires the view to still exist, you can do it in viewDidUnload.
If you use assign, you don't have to set the property to nil in viewDidUnload and dealloc (though it would be good practice). However, by the time you receive viewDidUnload, the view has already been released, so you can't use it at that point for other cleanup. Instead you have to override didReceiveMemoryWarning to do the cleanup before calling [super didReceiveMemoryWarning].
In iOS 5.0, you can do the cleanup in viewWillUnload instead of overriding didReceiveMemoryWarning.
Consider these two things:
There's no problem with retaining an object several times provided that each retain is balanced with release. With respect to properties, this just means that you should set your property to nil when you're done with it.
The basic idea behind memory management in Objective-C is that you worry about retaining the objects that you're using and let other objects worry about the objects that they're using.
Considering these, I'd advocate using retain. If you rely on he fact that a view retains its subviews, you've suddenly made your code dependant on external behavior. I'm not saying that UIView is likely to stop retaining its subviews, but if you keep a non-retained reference to a subview and later remove that subview from its superview you're code is likely to crash.
Some folks do use assign for outlets pointing to subviews when they know those subviews will never be removed. Personally, I don't see the point of relying on another object to retain something for you when retaining that thing yourself is so simple and cheap.

How does NSViewController avoid bindings memory leak? [have sample app]

I'm trying to implement my own version of NSViewController (for backwards compatibility), and I've hit a problem with bindings: Since bindings retain their target, I have a retain circle whenever I bind through File's owner.
So I thought I'd just explicitly remove my view from its superview and release the top level objects, and that would take care of the bindings, because my controller isn't holding on to the views anymore, so they release me and I can go away. But for some reason, my view controller still doesn't get released. Here's a sample app exhibiting the problem:
http://dl.dropbox.com/u/34351/BindingsLeak.zip
Build it, launch it, and hit Cmd-K ("Create Nib" in "Edit" menu) to load a NIB into the empty window. Hit Cmd-K again to release the first view controller (TestNibOwner) and load a new one. The old view controller never gets dealloced, though.
Remove the "value" binding on the checkbox, and it gets released just fine.
If you set breakpoints at the release/retain/autorelease overrides, you see that _NSBindingInfo retains the TestNibOwner, but never releases it in the leaking case.
Anyone know how to fix this?
Doing a little investigation with class-dump and friends, it looks like Apple has a private class called NSAutounbinder that takes care of this dirty work for classes such as NSViewController and NSWindowController. Can't really tell how it works or how to replicate it though.
So, I can't really answer your question on how to prevent the retain cycle from happening for arbitrary bindings in a loaded nib, but perhaps it's some consolation to know that Apple is cheating, and you're not missing anything obvious. :-)
One thing I've done for the same problem is to create a proxy NSObjectController inside my nib. My NSViewController-like class has a pointer to this proxy and all bindings are bound through it. When I want to cleanup the view controller, I then do [selfProxy setContent:nil] on the object controller and release the view controller. In this instance the NSObjectController proxy acts as the auto-unbinder in this case.
It's more manual and you can't just release the view by itself, but it does solve the retain problem.
I'd suggest you do this:
-(void) releaseTopLevelObjects
{
// Unbind the object controller's content by setting it to nil.
[selfProxy setContent:nil];
NSLog( #"topLevelObjects = %#", topLevelObjects );
[topLevelObjects release];
topLevelObjects = nil;
}
In your nib, bindings would happen through a path like:
selfProxy.content.representedObject.fooValue
When you remove your view from its superview, are you also sending it another -release message? It was created by unarchiving from the nib, right?

Removing an Observer

In a NSManagedObject Sub Class I have the code …
- (void) awakeFromInsert {
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:NSKeyValueObservingOptionNew context:nil];
}
Which adds my App Delegate as an Observer, what I want to do now is from inside my App Delegate, I want to remove itself as an Observer for my NSManagedObject Sub Class.
How would I do this?
Thanks.
I was thinking of adding this to my App Delegate
[JGManagedObject removeObserver:self forKeyPath:#"name"];
but unfortunately removeObserver:forKeyPath: is not a Class Method.
For something like this, it's probably best to rethink the design. The delegate, in this case, would have to have some specific knowledge of the managed object itself in order to do this -- and the delegate would have to have some idea about when in the lifecycle it should (or would want to) stop observing the object.
You have a few choices. Instead of doing this in awake from insert, you could have the delegate start observing it when it creates it and then stop observing it when it gives up ownership. If that is not feasible in your design, you could have the object remove its observer when it is deallocated. If this is a fire-and-forget (basically the delegate only cares once), you could remove the observer after the first change notification. Since, however, you created the observation within the creation lifecycle of this object, it is probably best to remove that observation at the destruction of the object:
- (void)dealloc
{
[self removeObserver:[NSApp delegate] forKeyPath:#"name"];
// other clean-up
[super dealloc];
}
You might also want to do this when the object awakes from fetch and from fault and release the observer when the object will become a fault.
Much the same way you added the observer in the first place, only with fewer options:
// Given some managed object "object"...
[object removeObserver:self forKeyPath:#"name"];
Note that we remove self as the observer, rather than the application delegate as given by [NSApp delegate], since the code will be running within the delegate itself.
How about sending your object the removeObserver:forKeyPath message just before you delete it from the ManagedObjectContext?
From iOS 11 and above, we have automatic registration of KVO. From the Foundation Release Notes for macOS 10.13 and iOS 11
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.