16

I've got a UITableView.

Here I got different cell's. Each cell has a model. With KVO and NotificationCenter the cell listen to the model for changes. When I leave the ViewController I get this error:

An instance 0x109564200 of class Model 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 0x109429cc0> (
<NSKeyValueObservance 0x109429c50: Observer: 0x10942d1c0, Key path: name, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x10968fa00>
)

In the cell I do this when the model property is set/changed:

[_model addObserver:self
         forKeyPath:@"name"
            options:0
            context:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(modelIsInvalid:)
                                             name:@"modelIsInvalid"
                                           object:_model];

Then in the cell's dealloc:

- (void)dealloc
{
    NSLog(@"DEALLOC CELL");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [_model removeObserver:self forKeyPath:@"name"];
}

In the model I also check when it get's deallocated:

- (void)dealloc
{
    NSLog(@"DEALLOC MODEL");
}

All cell's are deallocated before all the models, but still I get this error. Also I'm not sure how to set the breakpoint mentioned in the error.

Jogendra.Com
  • 6,394
  • 2
  • 28
  • 35

5 Answers5

8

It won't work because the cells are being reused. So when the cell goes off the screen it's not deallocated, it goes to reuse pool.

You shouldn't register notifications and KVO in cell. You should do it in table view controller instead and when the model changes you should update model and reload visible cells.

Souhaib Guitouni
  • 860
  • 1
  • 7
  • 24
Greg
  • 25,317
  • 6
  • 53
  • 62
  • This is what solved it, see my answer for how I did it (close to yours) –  Jul 31 '14 at 11:10
  • I have to disagree on "You shouldn't register notifications and KVO in cell". With the proper recipe, it's much more convenient to observe from the cell, and I do it all the time without issues. @Johannes has figured out the right way. – Mazyod Nov 30 '14 at 09:04
  • @Mazyod can you demonstrate the "proper recipe"? Preferably in swift ;) – natecraft1 Jul 26 '15 at 09:04
  • @natecraft1 I actually changed my mind, this answer is the way to go. Let your cell observe its model if needed, and the table view can observe the model for changes, as well. In a strict sense, KVO may not fit cells, delegation and callbacks would work better. – Mazyod Jul 26 '15 at 11:15
  • @Mazyod awesome thanks for the quick reply. Honestly though I'm pretty new to iOS and don't totally understand how to translate this to code. My guess is in the cellForRowAtIndexPath method in the TableViewController I add an observer to the object associated with that particular cell. Is that right? and if so, when do I remove the observer from that object? – natecraft1 Jul 26 '15 at 22:32
  • @natecraft1 Not really. Take a look at the cell display delegate methods. You get the callbacks: `willDisplayCell` and `didEndDisplayCell`. You want to add and remove the observers there. I hope the delegate methods are self-explanatory – Mazyod Jul 27 '15 at 10:23
  • An alternative is to remove the observer in "prepareforreuse" cell method, it works for me. – PakitoV Nov 10 '15 at 14:34
6

I found the answer. I can't delete the thread, someone has answered :) Maybe it will be useful for someone.

The problem is that the UITableView will dequeue the same cell used before, for a row longer down (that becomes visible when scrolling far enough).

In the setter I now have:

// Before we set new model
if (_model) {
    [_model removeObserver:self forKeyPath:@"name"];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"modelIsInvalid" object:_model];
}

_model = model;

[_model addObserver:self
         forKeyPath:@"name"
            options:0
            context:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(modelIsInvalid:)
                                             name:@"modelIsInvalid"
                                           object:_model];
4

Based on the accepted answer (which is correct) you can solve it by removing the observer on the "prepareForReuse" method of the cell.

That method will be called before reusing the cell on scrolling etc.. thus you wont have any problem.

- (void)prepareForReuse{
    [_model removeObserver:self forKeyPath:@"name"];
}
PakitoV
  • 2,476
  • 25
  • 34
0

There may be a possibility that your view Controller is not calling dealloc method because it's reference might be hold by someone and your dealloc method is not getting called. You can remove observer at your viewDidUnload: or viewWillDisappear: method or you can trace your controller into instrument for any retain

Retro
  • 3,985
  • 2
  • 17
  • 41
0

The best place to do this for Cells and Reusable views is in willMove(toSuperiew)

override func willMove(toSuperview newSuperview: UIView?) { if newSuperview == nil { // check for nil means this will be removed from superview self.collectionView?.removeObserver(self, forKeyPath: "contentSize") } }

Basheer_CAD
  • 4,908
  • 24
  • 36