KVC/KVO底层实现原理

KVC: Key-value coding is a mechanism for indirectly accessing anobject’s attributes and relationships using string identifiers.

所谓键值编码,并不是访问器方法的启动和实例变量的访问这种直接的方式,而是使用表示属性的字符串来间接访问对象属性值的一种结构。

只要存在访问器方法、声明属性或实例变量,就可以将其名字指定为字符串来访问。

之所以说键值编码的访问是接的:

1.   可以在运行中确定作为键的字符串

2.   使用者无法知道实际访问属性的方法

键值编码必需的方法在非正式协议NSKeyValueCoding中声明(头文件Foundation/NSKeyValueCoding.h)。默认在NSObject中实现。

下面就以下两个方法的调用进行说明:

-      (id)  valueForKey: (NSString *) key

返回表示属性的键字符串所对应的值。如果不能取得值,则将引起接收器调用方法valueForUndefinedKey:。

-      (void)setValue: (id) value  forKey: (NSString*) key

将键字符串key所对应的属性的值设置为value。不能设定属性时,将引起接收器调用方法setValue:ForUndefinedKey:。

执行时,有访问器的属性会使用访问器,没有访问器的属性也可以设定值和访问。因为上面两个方法均为实例方法,可以在方法体内访问实例变量。

访问过程如下:

1.   接收器中如果有key访问器(或getKey、isKey、_key、_getKey、setKey)则使用它。

2.   没有访问器时,使用接收器的类方法accessInstanceVariablesDirectly来查询。返回YES时,如果存在实例变量key(或_key、isKey、_isKey等)则返回或设置其值。使用引用计数管理方式时,实例变量如果为对象,则旧值会被自动释放,新值被保存并代入。

+  (BOOL)accessInstanceVariablesDirectly

    通常定义为返回YES,可以在子类中改变。该类方法返回YES时,使用键值编码可以访问该类的实例变量。返回NO时不可以访问。只要该方法返回YES,实例变量的可视属性即使有@private修饰,也可以访问。

3.   既没有访问器也没有实例变量时,将引起接收器调用方法valueForUndefinedKey:或setValue:forUndefinedKey:。

-       (id) valueForUndefinedKey: (NSStirng *) key

不能取得键字符串对应的值时,从方法valueForKey:中调用该方法。默认情况下,该方法的执行会触发NSUndefinedKeyException。不过,通过在子类中修改定义,就可以返回其他对象。

-       (void) setValue: (id) value  forUndefinedKey: (NSString *) key

不能设置键字符串key对应的属性值时,从方法setValue:forKey中调用该方法。默认情况下,该方法的执行会触发异常NSUndefinedKeyException。不过,通过在子类中修改定义,可以返回其他对象。

4.   如果该返回值不是对象,则返回被适当的对象包装的值;设置值时也应先包装成相应的对象。

属性为对象时,该对象还可能持有属性。这时候可以用“.”连接表示键的字符串,这种表示方式称为键路径。只要能找到对象,点和键多长都没有关系。

-      (id) valueForKeyPath:(NSString *) keyPath

以点切分键路径,并使用第一个键向接收器发送valueForKey:方法。然后,再使用键路径的下一个键,向得到的对象发送valueForKey:方法,如此反复操作,返回最后获得的对象。

-      (void)setValue: (id) value  forKeyPath:(NSString *) keyPath

与valueForKeyPath:方法一样取出对象,这里只对路径中的最后一个键调用setValue:forKey:方法,并设定属性值为value。

KVOkey-value observing,是在KVC基础上实现的,当某个对象的属性发生改变时,通知其它对象的机制。仅仅在以KVC准则来访问访问器或实例变量的情况下,才可以监视属性的变化。在方法内直接改变实例变量的值时,就不能监视了。

具体KVC准则有一下三点:

1.   随访问器方法而改变。

2.   使用setValue:forKey:和键进行改变。此时也可能不经由访问器。

3.   使用setValue:forKeyPath:和键路径进行改变。此时也可能不经由访问器。不仅仅是最终的监视对象的属性,当路径中的属性发生变化时,也会被通知。

KVO中的常用方法如下:

注册键值观察的方法:

-      (void) addObserver: (NSObject *)anObserver  forKeyPath (NSString *)keyPath

options: (NSKeyValueObservingOptions)options

context: (void *) context

从接收器的角度来看,监视键路径keyPath中的某个属性,要在接收器中注册。观察者为对象anObserver  。属性变化时发送的通知消息中,包含着显示变化内容的字典数据、参数context中指定的任意指针(或对象)。options中指定字典数据中包含什么样的值。值可取下面的常数或它们的异或运算。

NSKeyValueObservingOptionNew     ---- >提供属性改变后的值

NSKeyValueObservingOptionOld       ---->提供属性改变前的值

      移除已注册的观察:

-       (void) removeObserver: (NSObject *) anObserver

 forKeyPath: (NSString *) keyPath

移除观察者anObserver对于某个路径keyPath的观察。

      观察者需要实现接受通知的方法:

-       (void) observerValueForKeyPath: (NSString *)keyPath  ofObject: (id) object

Change: (NSDictionary *) change  context: (void *) context

从object的角度来看,当键路径keyPath的属性发生变化时会发送通知。字典change中保存着改变的相关信息。参数context中返回注册观察者时指定的值。


下面通过实验的方式来探索KVO的实现机制:

其实KVO是通过isa-swizzling技术实现的,主要的操作如下:

1.当为某个对象添加观察者的时候,该对象的类将被继承生成一个中间类,并使该对象的isa指针指向中间类(所以,有时候发送消息需要明确指定类型)。注意:同一个类的其它实例对象并不受影响。

2.中间类在被观察的属性的setter方法中,在改变属性值的前后分别添加了willChangeValueForKey:和didChangeValueForKey:。使其在通过KVC标准改变属性值时可以被观察到,并向观察者发送消息。

3.当移除对某个对象的所有观察后,该对象的isa指针会重新指向原有的类。

做如下验证:


可以得到结果:


说明person对象的isa指向的类对象的确在改变。

官方文档中的说明是willChangeValueForKey:和didChangeValueForKey:这两个方法必需成对调用,其实完全可以分别调用(当然你要明白分开调用意味着什么),甚至调用时两个方法的key都可以不同。例如:

[selfwillChangeValueForKey:@"age"];

[selfdidChangeValueForKey:@"name"];

经过我的“黑盒测试”,我的结论是:willChangeValueForKey:方法用于设置将要发送通知内容中与值改变之前相关的内容。如果不调用该方法,则前后两次接收到的消息内容中old值将会相同。didChangeValueForKey:方法中主要的工作是查看key值是否被观察,如果被观察则设置新值、发送通知消息;否则将不发送消息。

例如,实现的响应函数如下


有一个手动实现KVO的函数如下


则有结果:



若change做如下改变:


则有结果:


注释掉willChangeValueForKey:方法后old值将不再更新。

有一点需要注意的是,以下的调用方式会引起死循环:


扩展:依赖登记

有时候需要某属性值随着同一对象的其他属性的改变而改变。可以通过事先将这样的依赖关系在类中注册,那么即便属性值间接地发生了改变,也会发送通知消息。

需实现方法如下:

-       (void)setKeys: (NSArray*) keys 

triggerChangeNotificationsForDependentKey:(NSString *) dependentKey

数组keys中可以保存多个键。注册依赖关系,使当这些键中任意一个键对应的属性发生改变时,都会自动引起与键dependentKey的属性变化时一样的行为(被监视时发送通知)。

这种依赖登记的方式感觉有时候也是非常有用的呢(*^-^*)


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值