KVO key-value observing,即键值观察,kvo是一种监听机制,它将观察的指定对象属性更改后通知到观察者。
kvo 与通知
相同点
两者都是观察者模式,都是监听,都是一对多
不同点
kvo只用于监听属性的变化,而且只能通过 NSString 查找,编译器不会检查 NSString 的对错 而nsnotifacation 发送消息实际可以自己控制,而 kvo 只要属性变化就会自动调用。
kvo基本使用流程
- 注册观察者
- 实现 kvc 回调
- 移除观察者
注意事项
添加和移除通知要成对出现,如果删除未注册的通知会报异常 nsrangeException 当观察结束,观察者不会自己移除,如果条件触发将忽略观察者状态继续发送通知,这样如果观察者被销毁则会出现内存访问异常 协议里没有提供询问对象是观察者还是被观察者的方法,所以为了避免错误,将使观察者在初始化期间注册为观察者,并在释放期间注销注册(delloc)
kvo自动开关和 手动开关
+(Bool)automaticallyNotifiesObserversForKey:(nsstring *)key; 系统默认 return YES,默认开启,如果设置NO 就是关闭,需要手动去开启
- (void)setName:(NSString *)name{ [self willChangeValueForKey:@"name"]; _name = name; [self didChangeValueForKey:@"name"]; }
kvo 监听一对多
通过注册kvo观察者,对多个属性进行监听,比如下载进度
kvo观察可变数组
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
kvo 此层原理
官方文档说明:
- kvo是使用的一种isa-swizzing的技术实现的
- isa指针,是指向维护分配表的对象的类,这个分配表本质上指向了类实现的方法和其他数据的指针
- 当观察者为对象的属性注册时,被观察者的isa将被修改,指向中间的类而不是真正的类,因此isa指针的值不一定反应实例的实际类
- 所以我们不应该依赖isa指针来确定成员关系,而是应该使用类方法确定实例对象的类 通过上面的文档我们知道kvo是通过isa-swizzing来实现的,而在对对象属性进行注册时生成中间类 并将isa指针指向中间类
kvo只对属性进行观察,不会对成员变量进行观察,因为成员变量没有setter方法,而属性有。而kvo就是观察setter方法才发出通知的。
中间类
kvo注册后生成中间类,中间类就是 NSKVONotifying_类名,同时将实例对象的isa指针指向了中间类。而这个中间类是被观察者类的子类
中间类中的方法
setName:,class,dealloc,_isKVOA(判断当前类是否是kvo) 且是中间类重写了父类的这几个方法。
当delloac移除观察者,isa重新指回被观察者类,虽然 KVO销毁,但是 NSKVONotifying_类名并不会从内存中移除,原因是考虑重用的问题(kvo再次注册),中间类一旦注册到内存中去,就不会被销毁。
自定义kvo实现属性的监听
采用函数式编程思想实现自定义 kvo:
- 将注册和响应过程合并,通过函数式编程思想即block的方式进行响应
- 去掉系统kvo的手动销毁机制,改为自动销毁 具体分三步:
- 注册观察者
- kvo响应,通过重写 set 方法,在中间类的 setter 方法中,通过 block方式传递给外界进行响应。
- 移除观察者
注册观察者
- 判断当前观察者是否存在 setter 方法,防止成员变量进入
- 动态生成中间类,将需要重写的class方法 和 setter方法 添加到中间类中
- 将isa指向中间类
- 保存传入的信息,使用数据模型
kvo的响应
- 系统方法里,kvo的响应是通过中间类的setter方法,所以我们需要向中间类动态添加 setter 方法,为了在setter方法中向父类发送消息,通知属性值发生变化。
- 将setter方法重写添加打字类中(在注册观察者方法中添加)
- 通过将系统的objc_msgSendSuper 强制转换自定义的消息发送 my_msgSendSuper
- 通知观察者去响应
移除观察者
- 为了避免外界不断的调用removeObserver,在自定义kvo中实现自动移除观察者
- 实现 my_removeObserver:forke yP阿华田:,主要是清空数组,将isa指针改回原来的类
- 在自类中重写dealloc方法,当子类销毁时,会自动调用dealloc方法(在动态生成子类的方法中添加)