Search code examples
ioskey-value-observingdealloc

Class is deallocating while KVOs are still registered


I've been trying to pinpoint the problem but have not been able to do so because it's very hard to reproduce. Here's the error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x1455c7fa0 of class JASidePanelViewController was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x170456680> ( <NSKeyValueObservance 0x1702c3720: Observer: 0x1455c7fa0, Key path: state, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x17024a080> )'

From the error message, I can try to remove observers before an instance is deallocated. Here's the code:

SidePanelViewController.h

#import "JASidePanelController.h"
@interface SidePanelViewController : JASidePanelController
@end

SidePanelViewController.m

...
@implementation SidePanelViewController
...
- (void)viewDidLoad {
    [super viewDidLoad];
    [self addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)viewDidUnload:(BOOL)animated {
    [self viewDidUnload:animated];
    [self removeObserver:self forKeyPath:@"state"];
}
...
@end

Update: Doing some more research, I've discovered that viewDidUnload is not a reliable way of removing an observer because it's not always called. Thus, there are 2 possible solutions: 1) create the observer in viewDidLoad while pairing its removal in a -(void) dealloc, or 2) create and remove observers in viewDidAppear and viewWillDisappear (as recommended by Gargoyle).


Solution

  • Actually, I ended up pairing create and remove observers within viewDidLoad and dealloc instead of viewDidAppear and viewWillDisappear:

    @implementation SidePanelViewController
    
    ...
    
    - (void)viewDidLoad{
        [super viewDidLoad];
        [self addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:nil];
    }
    
    - (void) dealloc{
        [self removeObserver:self forKeyPath:@"state"];
    }
    
    ...
    
    @end
    

    Apparently, there are various scenarios where viewWillDisappear was not called and thus, when I used viewDidAppear and viewWillDisappear to create/remove observers, I ended getting the following error at various times:

    *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer for the key path "state" from because it is not registered as an observer.'