Reputation: 4602
I am looking at a table view cell here and I find this code:
- (void)awakeFromNib {
[super awakeFromNib];
[self addObserver:self forKeyPath:@"model.isDownloading" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"model.isCached" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"model.isOutDated" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"model.cacheUpdateDate" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"model" options:NSKeyValueObservingOptionNew context:NULL];
}
The observers are removed in the dealloc
method. model
is a weak
property, receiving a managed object (core-data).
I receive spurious crashes, telling me, that the managed object is deleted, but still has observers registered.
Why the error happens is pretty clear to me: The object is removed somewhere in the background, but still linked into the tableview's cell. Since dealloc
on the cell is basically never called during lifetime of the application, the observers are never really removed. Since the reference to the core-data object is weak
, it will deallocate silently in the background - at least try to. This fails, because the model is still observed.
I have some questions:
model
object, not in the setter in self
, is that correct?model
is reassigned (self.model = newThing
requires, that removeObserver
is called on model
before assigning newThing
, and observers need to be registered on newThing
after that).model
strong instead of weak
, and of course make sure, that it is properly set to nil
in prepareForReuse:
. Does this have side effects, that I have not realized?The error message is:
class xxx was deallocated while key value observers were still registered with it
Upvotes: 0
Views: 250
Reputation: 5698
If a path like "model.isDownloading" is observed, then the observer is registered in the model
object, not in the setter in self
, is that correct?
This is a very good question. As far as I knew previously, the runtime keeps tracks of at least 2 things when an object is registered with KVO
1) The object that is observing and
2) the keypath of a property it is observing.
We know the runtime overrides the setter of the property to notify the objects observing of a change.
But clearly the runtime must also keep track of the object that is being observed, otherwise how else would it know if it is being released while there are still observers registered?
It appears that the runtime parses the keyPath
for dots, and follows the reference chain from the receiver (self
in your example), to keep track of the observed object (self.model
)
Is objC smart enough to handle the observer change if model
is reassigned (self.model = newThing
requires, that removeObserver
is called on model
before assigning newThing
, and observers need to be registered on newThing
after that).
No it is not smart enough. What happens is that the runtime subclasses your self.model
object (Let's say it's of type Model
) to override the setter for isDownloading
, for example. Now your self.model
object is of type NSKVONotifying_Model
. If you were to swap out your self.model
pointer to point to a new object of type Model
, it would not be of the same KVO class that the runtime created. Thus the setter for the property would not have the instructions added to notify observers.
So yes you have to remove observers on the first object and add it to the second object, even though you are using the same pointer variable.
Since the crash happens on dealloc of the managed object, I think a simple solution would be, to make model
strong instead of weak
, and of course make sure, that it is properly set to nil
in prepareForReuse:
. Does this have side effects, that I have not realized?
This would be correct, but as you know you would have to re-add observers if your referenced model
object gets swapped out.
Another alternative (if you can change the Model
class), is to add a reference in the Model
class back to your self
in this context. Then in the init
/dealloc
of your Model
class, you can add itself as an observer to your new reference.
Last note is that if you ever catch yourself sending an addObserver
message where the receiver and the observer object are the same-- you may as well just override the setter yourself.
In your example, you could just override -setModel
to do whatever you were going to do in your observation notification handler (-observeValueForKey:::
)
Upvotes: 1