一、KVO
Key-Value Observing 键值用于检测对象的某些属性的实时变化情况并作出响应
KVO实现
/** KVO 实现 */ self.womanPerson = [[WLPerson alloc] init]; [self.womanPerson addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; self.womanPerson.name = @"rose"; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { /** 同一个类里可能有多个KVO */ if (object == self.person && [keyPath isEqualToString:@"name"]) { WLPerson *person = object; NSLog(@"---->%@", person.name); } else if (object == self.womanPerson && [keyPath isEqualToString:@"name"]) { WLPerson *person = object; NSLog(@"---->%@", person.name); } }
在为一个对象添加观察者之时,framework使用runtime动态创建了一个WLPerson类的子类NSKVONotifying_WLPerson,而为了不让外部知道这一行为,NSKVONotifying_WLPerson重写了-(void)class方法返回之前的类
self.person = [[WLPerson alloc] init]; NSLog(@" -添加监听者之前-.class-->%@ ---isa--->%s", NSStringFromClass(self.person.class), object_getClassName(self.person)); object_getClass(self.person); [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; NSLog(@" -添加监听者之后-.class-->%@ ---isa--->%s", NSStringFromClass(self.person.class), object_getClassName(self.person)); [self.person removeObserver:self forKeyPath:@"name" context:nil]; NSLog(@" -移除监听者之后-.class-->%@ ---isa--->%s", NSStringFromClass(self.person.class), object_getClassName(self.person)); 打印: -添加监听者之前-.class-->WLPerson ---isa--->WLPerson -添加监听者之后-.class-->WLPerson ---isa--->NSKVONotifying_WLPerson -移除监听者之后-.class-->WLPerson ---isa--->WLPerson
可以看到在添加观察之后.class打印出来的类没有变化,使用object_getClassName()打印出来的类是NSKVONotifying_WLPerson ,因为这个object_getClass()返回的是这个对象的isa指针,isa指针指向的一定是这个对象所属的类。
二、常见错误
1、romove 观察者
[self removeObserver:self forKeyPath:@"name" context:nil]; *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x121d099d0> for the key path "name" from <ViewController 0x121d099d0> because it is not registered as an observer.' 原因:crash信息已经很清楚了,谁添加的观察者,谁移除
2、重复移除观察者
[self removeObserver:self forKeyPath:@"name" context:nil]; [self removeObserver:self forKeyPath:@"name" context:nil]; crash *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x100306e50> for the key path "name" from <WLPerson 0x17400a740> because it is not registered as an observer.’ 解决:网上有很多方法,最简单的try catch @try { [self.person removeObserver:self forKeyPath:@"name" context:nil]; [self.person removeObserver:self forKeyPath:@"name" context:nil]; } @catch (NSException *exception) { NSLog(@"重复移除"); } @finally { } 可能有人会问了,谁这么傻逼移除两次,其实在很多情况下确实写的移除一次,但是可能。。。。。比如SDPhotobrowser就存在这样的crash
3、属性的值修改了的信息收到了,但是没有处理 方法-observeValueForKeyPath:ofObject:change:context:却没有实现,只要你注册了观察者,这个方法必须实现
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<ViewController: 0x10040a1e0>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. Key path: name Observed object: <WLPerson: 0x170005d00> Change: { kind = 1; new = jack; } Context: 0x0’
4、添加和移除时,content上下文不一致
[self.person removeObserver:self forKeyPath:@"name" context:@"随便给个"]; *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <ViewController 0x100704960> for the key path "name" from <WLPerson 0x1740046f0> because it is not registered as an observer.’ ps:一般传nil
5、给局部变量添加观察者
WLPerson *tempPerson = [[WLPerson alloc] init]; [tempPerson addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; tempPerson.name = @"jack”; *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x174000880 of class WLPerson was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x174022800> ( <NSKeyValueObservance 0x174045a00: Observer: 0x100307e70, Key path: name, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x174045be0> )'
未完待续。。。