热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

KVC/KVO底层实现原理

KVC:Key-valuecodingisamechanismforindirectlyaccessinganobject’sattributesandrelations

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的属性变化时一样的行为(被监视时发送通知)。

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



推荐阅读
  • 数据管理权威指南:《DAMA-DMBOK2 数据管理知识体系》
    本书提供了全面的数据管理职能、术语和最佳实践方法的标准行业解释,构建了数据管理的总体框架,为数据管理的发展奠定了坚实的理论基础。适合各类数据管理专业人士和相关领域的从业人员。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 在前两篇文章中,我们探讨了 ControllerDescriptor 和 ActionDescriptor 这两个描述对象,分别对应控制器和操作方法。本文将基于 MVC3 源码进一步分析 ParameterDescriptor,即用于描述 Action 方法参数的对象,并详细介绍其工作原理。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • 在使用 DataGridView 时,如果在当前单元格中输入内容但光标未移开,点击保存按钮后,输入的内容可能无法保存。只有当光标离开单元格后,才能成功保存数据。本文将探讨如何通过调用 DataGridView 的内置方法解决此问题。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文介绍了在Windows环境下使用pydoc工具的方法,并详细解释了如何通过命令行和浏览器查看Python内置函数的文档。此外,还提供了关于raw_input和open函数的具体用法和功能说明。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 深入理解C++中的KMP算法:高效字符串匹配的利器
    本文详细介绍C++中实现KMP算法的方法,探讨其在字符串匹配问题上的优势。通过对比暴力匹配(BF)算法,展示KMP算法如何利用前缀表优化匹配过程,显著提升效率。 ... [详细]
author-avatar
u47871838
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有