KVO

180 阅读4分钟

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的手动销毁机制,改为自动销毁 具体分三步:
  1. 注册观察者
  2. kvo响应,通过重写 set 方法,在中间类的 setter 方法中,通过 block方式传递给外界进行响应。
  3. 移除观察者

注册观察者

  • 判断当前观察者是否存在 setter 方法,防止成员变量进入
  • 动态生成中间类,将需要重写的class方法 和 setter方法 添加到中间类中
  • 将isa指向中间类
  • 保存传入的信息,使用数据模型

kvo的响应

  • 系统方法里,kvo的响应是通过中间类的setter方法,所以我们需要向中间类动态添加 setter 方法,为了在setter方法中向父类发送消息,通知属性值发生变化。
  • 将setter方法重写添加打字类中(在注册观察者方法中添加)
  • 通过将系统的objc_msgSendSuper 强制转换自定义的消息发送 my_msgSendSuper
  • 通知观察者去响应

移除观察者

  • 为了避免外界不断的调用removeObserver,在自定义kvo中实现自动移除观察者
  • 实现 my_removeObserver:forke yP阿华田:,主要是清空数组,将isa指针改回原来的类
  • 在自类中重写dealloc方法,当子类销毁时,会自动调用dealloc方法(在动态生成子类的方法中添加)