Reputation: 11005
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?
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
Upvotes: 4
Views: 2900
Reputation: 18363
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];
}
Upvotes: 3
Reputation: 5128
Not sure why you're facing the problem.The most short cut way i do is by setting the custom observer to 'nil' in the dealloc ;) ,but in your case to use that shortcut way also you need to add the obeserver in some other way around.
Ok at least i can tell you is,in your dealloc,
-(void)dealloc{
[[NSNotificationCenter default center] removeObserver: self];
[self removeObserver:self forKeyPath:@"inputStateControl"];
//OR
//If you had create a observer called "temp",then easy way to remove the observer is temp=nil;
}
If you're still hanging in,put the @try @catch blocks for the temporary solution,note that its not going to remove the observer ;)...
Not the perfect the answer you were looking for,but its just the way i think...Happy coding :-)
Upvotes: 0
Reputation: 3613
While not an ideal solution by any means, surrounding the remove with a try-catch block will resolve your issue. If in fact the observer is not registered, ignoring an exception during removing it is safe. The main risk is whatever assumption your app is relying on that it IS registered.
Upvotes: 0
Reputation: 7102
I don't know exactly why what you're doing isn't working, but having an object observe itself like this strikes me as a bad idea. You should just implement your inputStateControl's setter explicitly (setInputStateControl
) and do your logging and whatever other side effects you want in that setter method.
Upvotes: 1