thst
thst

Reputation: 4602

keyValue observer from cell to managed object

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:

The error message is:

class xxx was deallocated while key value observers were still registered with it

Upvotes: 0

Views: 250

Answers (1)

A O
A O

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

Related Questions