Search code examples
iosobjective-ckey-value-observing

Crash when removeObserver for an Integer property?


I create a CustomView:UIView with XIB, load and addObserver for a NSInteger property like that:

//CustomView.h

@interface CustomView : UIView
    @property (nonatomic) NSInteger inputStateControl;
@end

//CustomView.m

static void *kInputStateControlObservingContext = &kInputStateControlObservingContext;
@implementation CustomView
- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:self options:nil];
        self = [nib objectAtIndex:0];
        //
        [self commonInit];
    }
    return self;
}

-(void)commonInit{
[self addObserver:self forKeyPath:@"inputStateControl" options:NSKeyValueObservingOptionOld context:kInputStateControlObservingContext];
}

#pragma mark Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if ( context == kInputStateControlObservingContext ) {
        NSInteger oldState = [[change objectForKey:NSKeyValueChangeOldKey] integerValue];
        if ( oldState != self.inputStateControl ) {
            NSLog(@"CONTEXT change %i to %i",oldState,self.inputStateControl);
        }
    }
    else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

-(void)dealloc{
    [self removeObserver:self forKeyPath:@"inputStateControl"];
//    [self removeObserver:self forKeyPath:@"inputStateControl" context:kInputStateControlObservingContext];
}

@end

Everything work OK if I comment out removeObserver in dealloc, here is the log:

CONTEXT change 0 to 2

But when removeObserver, App crash:

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

No crash when comment load CustomView.xib but nothing to do without XIB. What's wrong in my code?

How to add and removeObserver for an NSInteger property inside CustomView with Custom Xib?

Thanks in advance!

*EDIT: I add my code to make my question clear. Please help!

https://github.com/lequysang/github_zip/blob/master/CustomViewKVO.zip


Solution

  • Here's what's happening - in your viewDidLoad method, you call [[CustomView alloc] init]. This creates a new CustomView instance, and calls init on it. However, in init, you load a new instance from the nib and replace self with the one from the nib. This causes the instance that you created from alloc and set up with the self = [super init]; to be deallocated as there are no more strong references to it. Since this instance is deallocated before calling commonInit, it never observes its own properties so removing itself as an observer causes the exception.

    One way to fix this would be to just load the view directly from the nib in your view controller, or create a class method on CustomView

    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:nil options:nil];
    CustomView *customView = topLevelObjects[0];
    

    If you do take that approach, discard you init implementation and replace it with an initWithCoder: that does this:

    - (id)initWithCoder:(NSCoder *)aDecoder
    {
        self = [super initWithCoder:aDecoder];
        if (self) {
            _inputStateControl = 0;
            [self commonInit];
        }
    
        return self;
    }
    

    The reason for implementing initWithCoder: is that it will be called automatically when you load the view from the nib. You just have to implement it and you will be able to do the setup that you are already doing in init. Also make sure your dealloc is implemented like so:

    -(void)dealloc{
        [self removeObserver:self forKeyPath:@"inputStateControl" context:kInputStateControlObservingContext];
    }