我创建一个CustomView:UIView with XIB,load和addObserver for NSInteger属性,如:
// 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
如果我在dealloc中注释掉removeObserver,那么一切正常,这里是日志:
CONTEXT change 0 to 2
但是当removeObserver,App崩溃时:
*** 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.'
注释加载CustomView.xib时没有崩溃,但没有XIB没有任何事情可做。 我的代码有什么问题?
提前致谢!
*编辑:我添加我的代码以使我的问题清楚。请帮忙!
https://github.com/lequysang/github_zip/blob/master/CustomViewKVO.zip
答案 0 :(得分:3)
以下是正在发生的事情 - 在viewDidLoad方法中,您调用[[CustomView alloc] init]
。这将创建一个新的CustomView实例,并在其上调用init
。但是,在init
中,您将从nib加载新实例,并将self
替换为nib中的实例。这会导致您从alloc
创建并使用self = [super init];
设置的实例被取消分配,因为没有更多强引用。由于在调用commonInit
之前释放了此实例,因此它永远不会观察到自己的属性,因此将自身移除为观察者会导致异常。
解决此问题的一种方法是直接从视图控制器中的nib加载视图,或者在CustomView上创建类方法
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:nil options:nil];
CustomView *customView = topLevelObjects[0];
如果您确实采用了这种方法,请放弃init
实施,并将其替换为执行此操作的initWithCoder:
:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_inputStateControl = 0;
[self commonInit];
}
return self;
}
实现initWithCoder:
的原因是从nib加载视图时会自动调用它。您只需要实现它,您就可以进行init
中已经进行的设置。还要确保你的dealloc是这样实现的:
-(void)dealloc{
[self removeObserver:self forKeyPath:@"inputStateControl" context:kInputStateControlObservingContext];
}
答案 1 :(得分:1)
我不知道完全为什么你所做的事情不起作用,但让一个物体像这样观察自己是一个坏主意。您应该只显式实现inputStateControl的setter(setInputStateControl
),并在该setter方法中执行日志记录和其他任何副作用。
答案 2 :(得分:0)
虽然不是任何理想的解决方案,但使用try-catch块围绕删除将解决您的问题。如果实际上观察者没有注册,则在删除它时忽略异常是安全的。主要风险是您的应用程序依赖于它已注册的任何假设。
答案 3 :(得分:0)
不确定为什么你会遇到这个问题。我做的最捷径的方法是在dealloc中将自定义观察者设置为'nil';)但是在你的情况下使用那种快捷方式也需要添加以某种其他方式观察者。
好吧,至少我可以告诉你,在你的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;
}
如果你还在,请将@try @catch块放到临时解决方案中,注意它不会删除观察者;)...
不是你想要的答案,但它只是我想的方式......快乐的编码: - )