(译)KVO的内部实现

09年的一篇文章,比较深入地阐述了KVO的内部实现。
 
KVO是实现Cocoa Bindings的基础,它提供了一种方法,当某个属性改变时,相应的objects会被通知到。在其他语言中,这种观察者模式通常需要单独实现,而在Objective-C中,通常无须增加额外代码即可使用。
 
概览
这是怎么实现的呢?其实这都是通过Objective-C强大的运行时(runtime)实现的。当你第一次观察某个object时,runtime会创建一个新的继承原先class的subclass。在这个新的class中,它重写了所有被观察的key,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是哪种类型的object)。所以object神奇地变成了新的子类的实例。
 
这些被重写的方法实现了如何通知观察者们。当改变一个key时,会触发setKey方法,但这个方法被重写了,并且在内部添加了发送通知机制。(当然也可以不走setXXX方法,比如直接修改iVar,但不推荐这么做)。
 
有意思的是:苹果不希望这个机制暴露在外部。除了setters,这个动态生成的子类同时也重写了-class方法,依旧返回原先的class!如果不仔细看的话,被KVO过的object看起来和原先的object没什么两样。
 
深入探究
下面来看看这些是如何实现的。我写了个程序来演示隐藏在KVO背后的机制。
  1. // gcc -o kvoexplorer -framework Foundation kvoexplorer.m 
  2.      
  3. #import <Foundation/Foundation.h> 
  4. #import <objc/runtime.h> 
  5.  
  6.  
  7. @interface TestClass : NSObject 
  8. { 
  9.     int x; 
  10.     int y; 
  11.     int z; 
  12. } 
  13. @property int x; 
  14. @property int y; 
  15. @property int z; 
  16. @end 
  17.  
  18. @implementation TestClass 
  19. @synthesize x, y, z; 
  20. @end 
  21.  
  22. static NSArray *ClassMethodNames(Class c) 
  23. { 
  24.     NSMutableArray *array = [NSMutableArray array]; 
  25.      
  26.     unsigned int methodCount = 0; 
  27.     Method *methodList = class_copyMethodList(c, &methodCount); 
  28.     unsigned int i; 
  29.     for(i = 0; i < methodCount; i++) 
  30.         [array addObject: NSStringFromSelector(method_getName(methodList[i]))]; 
  31.     free(methodList); 
  32.      
  33.     return array; 
  34. } 
  35.  
  36. static void PrintDescription(NSString *name, id obj) 
  37. { 
  38.     NSString *str = [NSString stringWithFormat: 
  39.         @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>", 
  40.         name, 
  41.         obj, 
  42.         class_getName([obj class]), 
  43.         class_getName(obj->isa), 
  44.         [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]]; 
  45.     printf("%s\n", [str UTF8String]); 
  46. } 
  47.  
  48. int main(int argc, char **argv) 
  49. { 
  50.     [NSAutoreleasePool new]; 
  51.      
  52.     TestClass *x = [[TestClass alloc] init]; 
  53.     TestClass *y = [[TestClass alloc] init]; 
  54.     TestClass *xy = [[TestClass alloc] init]; 
  55.     TestClass *control = [[TestClass alloc] init]; 
  56.      
  57.     [x addObserver:x forKeyPath:@"x" options:0 context:NULL]; 
  58.     [xy addObserver:xy forKeyPath:@"x" options:0 context:NULL]; 
  59.     [y addObserver:y forKeyPath:@"y" options:0 context:NULL]; 
  60.     [xy addObserver:xy forKeyPath:@"y" options:0 context:NULL]; 
  61.      
  62.     PrintDescription(@"control", control); 
  63.     PrintDescription(@"x", x); 
  64.     PrintDescription(@"y", y); 
  65.     PrintDescription(@"xy", xy); 
  66.      
  67.     printf("Using NSObject methods, normal setX: is %p, overridden setX: is %p\n", 
  68.           [control methodForSelector:@selector(setX:)], 
  69.           [x methodForSelector:@selector(setX:)]); 
  70.     printf("Using libobjc functions, normal setX: is %p, overridden setX: is %p\n", 
  71.           method_getImplementation(class_getInstanceMethod(object_getClass(control), 
  72.                                    @selector(setX:))), 
  73.           method_getImplementation(class_getInstanceMethod(object_getClass(x), 
  74.                                    @selector(setX:)))); 
  75.      
  76.     return 0; 
  77. } 
 
我们从头到尾细细看来。
 
首先定义了一个TestClass的类,它有3个属性。
 
然后定义了一些方便调试的方法。ClassMethodNames使用Objective-C运行时方法来遍历一个class,得到方法列表。注意,这些方法不包括父类的方法。PrintDescription打印object的所有信息,包括class信息(包括-class和通过运行时得到的class),以及这个class实现的方法。
 
然后创建了4个TestClass实例,每一个都使用了不同的观察方式。x实例有一个观察者观察xkey,y, xy也类似。为了做比较,zkey没有观察者。最后control实例没有任何观察者。
 
然后打印出4个objects的description。
 
之后继续打印被重写的setter内存地址,以及未被重写的setter的内存地址做比较。这里做了两次,是因为-methodForSelector:没能得到重写的方法。KVO试图掩盖它实际上创建了一个新的subclass这个事实!但是使用运行时的方法就原形毕露了。
 
运行代码
 
看看这段代码的输出
  1. control: <TestClass: 0x104b20> 
  2.     NSObject class TestClass 
  3.     libobjc class TestClass 
  4.     implements methods <setX:, x, setY:, y, setZ:, z> 
  5. x: <TestClass: 0x103280> 
  6.     NSObject class TestClass 
  7.     libobjc class NSKVONotifying_TestClass 
  8.     implements methods <setY:, setX:, class, dealloc, _isKVOA> 
  9. y: <TestClass: 0x104b00> 
  10.     NSObject class TestClass 
  11.     libobjc class NSKVONotifying_TestClass 
  12.     implements methods <setY:, setX:, class, dealloc, _isKVOA> 
  13. xy: <TestClass: 0x104b10> 
  14.     NSObject class TestClass 
  15.     libobjc class NSKVONotifying_TestClass 
  16.     implements methods <setY:, setX:, class, dealloc, _isKVOA> 
  17. Using NSObject methods, normal setX: is 0x195e, overridden setX: is 0x195e 
  18. Using libobjc functions, normal setX: is 0x195e, overridden setX: is 0x96a1a550 
 
首先,它输出了controlobject,没有任何问题,它的class是TestClass,并且实现了6个set/get方法。
 
然后是3个被观察的objects。注意-class仍然显示的是TestClass,使用object_getClass显示了这个object的真面目:它是NSKVONotifying_TestClass的一个实例。这个NSKVONotifying_TestClass就是动态生成的subclass!
 
注意,它是如何实现这两个被观察的setters的。你会发现,它很聪明,没有重写-setZ:,虽然它也是个setter,因为它没有被观察。同时注意到,3个实例对应的是同一个class,也就是说两个setters都被重写了,尽管其中的两个实例只观察了一个属性。这会带来一点效率上的问题,因为即使没有被观察的property也会走被重写的setter,但苹果显然觉得这比分开生成动态的subclass更好,我也觉得这是个正确的选择。
 
你会看到3个其他的方法。有之前提到过的被重写的-class方法,假装自己还是原来的class。还有-dealloc方法处理一些收尾工作。还有一个_isKVOA方法,看起来像是一个私有方法。
 
接下来,我们输出-setX:的实现。使用-methodForSelector:返回的是相同的值。因为-setX:已经在子类被重写了,这也就意味着methodForSelector:在内部实现中使用了-class,于是得到了错误的结果。
 
最后我们通过运行时得到了不同的输出结果。
 
作为一个优秀的探索者,我们进入debugger来看看这第二个方法的实现到底是怎样的:
  1. (gdb) print (IMP)0x96a1a550 
  2. $1 = (IMP) 0x96a1a550 <_NSSetIntValueAndNotify> 
 
看起来是一个内部方法,对Foundation使用nm -a得到一个完整的私有方法列表:
  1. 0013df80 t __NSSetBoolValueAndNotify 
  2. 000a0480 t __NSSetCharValueAndNotify 
  3. 0013e120 t __NSSetDoubleValueAndNotify 
  4. 0013e1f0 t __NSSetFloatValueAndNotify 
  5. 000e3550 t __NSSetIntValueAndNotify 
  6. 0013e390 t __NSSetLongLongValueAndNotify 
  7. 0013e2c0 t __NSSetLongValueAndNotify 
  8. 00089df0 t __NSSetObjectValueAndNotify 
  9. 0013e6f0 t __NSSetPointValueAndNotify 
  10. 0013e7d0 t __NSSetRangeValueAndNotify 
  11. 0013e8b0 t __NSSetRectValueAndNotify 
  12. 0013e550 t __NSSetShortValueAndNotify 
  13. 0008ab20 t __NSSetSizeValueAndNotify 
  14. 0013e050 t __NSSetUnsignedCharValueAndNotify 
  15. 0009fcd0 t __NSSetUnsignedIntValueAndNotify 
  16. 0013e470 t __NSSetUnsignedLongLongValueAndNotify 
  17. 0009fc00 t __NSSetUnsignedLongValueAndNotify 
  18. 0013e620 t __NSSetUnsignedShortValueAndNotify 
这个列表也能发现一些有趣的东西。比如苹果为每一种primitive type都写了对应的实现。Objective-C的object会用到的其实只有__NSSetObjectValueAndNotify,但需要一整套来对应剩下的,而且看起来也没有实现完全,比如long dobule或_Bool都没有。甚至没有为通用指针类型(generic pointer type)提供方法。所以,不在这个方法列表里的属性其实是不支持KVO的。
 
KVO是一个很强大的工具,有时候过于强大了,尤其是有了自动触发通知机制。现在你知道它内部是怎么实现的了,这些知识或许能帮助你更好地使用它,或在它出错时更方便调试。
 
如果你打算使用KVO,或许可以看一下我的另一篇文章 Key-Value Observing Done Right
揭秘个人开发者接支付宝的‘野路子’:不用企业资质,用Python模拟扫码转账与回调监听 本文探讨了个人开发者如何通过Python模拟支付宝扫码转账与回调监听的技术实现,解析了支付宝URL协议格式,并设计了本地化的支付状态监听方案。文章强调该方法仅用于技术研究,实际应用需遵守支付宝官方接口和金融合规要求。 阅读详情

相关推荐

SNN目标识别MNIST

总体来说这篇论文提出的方法用了多种更精密的细节,如LIF膜电位公式,三种STDP方法,但我觉得还可以有很大的改进,比如输入层全连接到兴奋层,兴奋层通过抑制层实现的侧向抑制,抑制层不参与学习等等。尤其是兴奋层通过抑制层实现侧向抑制这里,当神经元兴奋时,通过抑制层再倒回兴奋层,所抑制的已经经过一了一段时间的延迟。如有错误,请不吝赐教。

m0_74982304的博客 1262

KVO 底层原理

KVO,全称为Key-Value observing,中文名为键值观察,KVO是一种机制,它允许将其他对象的指定属性的更改通知给对象。 在Key-Value Observing Programming Guide官方文档中,又这么一句话:理解KVO之前,必须先理解KVC(即KVO是基于KVC基础之上,KVO底层也是由KVC实现的) In order to understand key-value observing, you must first understand key-value coding. K

CRMEB小程序商城的博客 6184

el-table通过上下左右键移动光标

在Element Plus的el-table中包含el-input和el-select组件,要实现上下左右键控制光标位置,需要通过focus和blur跟踪判断当前是哪个el-input或el-select有焦点,当按键按下时,通过监听,计算下一个获取焦点元素的索引,通过focus方法将焦点切换至改元素。

秋思的博客 1350

KVO底层实现原理

KVO 的实现依赖于 Objective-C 的 Runtime运行时机制 。 基本的原理: 当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。 深入剖析: Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,

fellow_gengxj的博客 499

高德地图爬虫

高德地图爬虫 工具:Pycharm,win10,Python3.6.4 1.需求分析 这篇爬虫和上一篇百度地图爬虫要求一样,百度地图爬虫我发现有一个auth参数会一直变化,一旦变化则获取的数据是不准确的,所以我上高德地图看了一下,高德地图没有这个反爬机制。但是高德地图大概爬取120个页面就会跳一次验证码,我这里是通过换IP解决这个问题的。 2.数据准备 同样我们需要全国地级市信息,用之前...

123的博客 1万+

Linux内核4.14版本——drm框架分析(1)——drm简介

drm框架分析

yangguoyu8023的博客 1万+

【iOS】KVO底层原理

【iOS底层原理】KVO

cheng_lin0201的博客 685

Anaconda安装教程(超详细版)

目录 一、Anaconda简介 二、运行环境 三、安装Anaconda 四、手动配置环境变量(重点) 五、测试Anaconda环境是否配置成功 一、Anaconda简介 Anaconda,一个开源的Python发行版本,可用于管理Python及其相关包,包含了conda、Python等180多个科学包及其依赖项。 二、运行环境 Windows11 (Window10用户亦可参考,仅环境变量打开位置不同)。 三、安装Anaconda (1)百度“Anaconda”或者输入网址

m0_61607990的博客 22万+

IOS KVO底层实现原理 (一)

IOS KVO底层实现原理 (一)一,KVO简述二,KVO探索三,KVO底层原理四,KVO底层实现代码 一,KVO简述 KVO的全称 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。 带着问题探索: iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) 如何手动触发KVO KVO 底层实现是什么? 修改成员变量的值会出发 KVO 吗? KV...

kyl282889543的博客 6627

EBSD数据转ABAQUS的inp文件实操指南

晶体塑性有限元模拟中,如何将材料微观组织表征数据转化为可计算的有限元模型,是连接实验与仿真的关键环节。EBSD技术能够获取材料表面的晶体取向、晶粒形貌和晶界分布信息,但这类数据需要经过格式解析、欧拉角转换、网格生成等步骤,才能被ABAQUS等有限元软件读取。通过将扫描点坐标映射为节点、按像素邻域生成单元,并将Bunge约定欧拉角变换为局部坐标系方向余弦,用户可以准确构建包含真实微观结构的inp文件模型。这一流程广泛应用于金属材料形变、织构演化及应力集中分析等研究场景,有效提升仿真结果与实验的一致性。围绕数据

weixin_42509513的博客 310

基于STM32的智能浇花系统毕业设计(全部资料)

摘要:本文设计了一种基于STM32单片机的智能浇花系统,集成了环境温湿度检测、土壤温湿度监测、LCD显示、Wi-Fi联网及手机APP远程控制等功能。系统通过传感器实时采集环境参数,自动控制水泵工作实现智能灌溉,同时支持用户通过手机APP远程监控和调节。论文详细阐述了硬件选型、软件设计及系统测试过程,验证了系统的稳定性和实用性。该设计为家庭园艺提供了智能化解决方案,具有节水高效、操作便捷等特点。关键词:STM32;智能灌溉;远程控制;环境监测;Wi-Fi通信。

3254968421的博客 1195

KVO 底层实现原理

一 、 基本使用 概述 KVO全称NSKeyValueObserving,是一个非正式协议,它定义了对象之间观察和通知状态改变的通用机制的。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。 在 Objective-C 和 Cocoa 中,有许多事件之间进行通信的方式,并且每个都有不同程度的形...

Walden_tinghou的专栏 874

2026快手爬虫终极指南:JA4+指纹绕过+sign全逆向+AES响应解密(附可运行代码)

快手2026反爬破解实战指南 本文深入剖析快手2026年最新反爬体系,提供完整的破解方案: 四层防御体系:从传输层(JA4+指纹)、应用层(sign参数)、设备层到行为层,90%的爬虫在前两层即被拦截 JA4+指纹绕过:使用curl_cffi模拟Chrome 124的TLS指纹,完美匹配真实浏览器特征 sign参数逆向:通过动态调试提取加密逻辑,Python实现MD5签名算法,包含参数排序和盐值处理 响应解密:破解AES-GCM加密接口数据,完整还原响应内容 技术亮点: 使用curl-impersonate

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1802

(译)KVO的内部实现[转载]

09年的一篇文章,比较深入地阐述了KVO的内部实现。 KVO是实现Cocoa Bindings的基础,它提供了一种方法,当某个属性改变时,相应的objects会被通知到。在其他语言中,这种观察者模式通常需要单独实现,而在Objective-C中,通常无须增加额外代码即可使用。 概览 这是怎么实现的呢?其实这都是通过Objective-C强大的运行时(runtime)实现的。当你第

Dale.Qi-We Can't Stop 1145

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

[转](译)KVO的内部实现

转载自:http://www.cocoachina.com/applenews/devnews/2014/0107/7667.html 09年的一篇文章,比较深入地阐述了KVO的内部实现。 KVO是实现Cocoa Bindings的基础,它提供了一种方法,当某个属性改变时,相应的objects会被通知到。在其他语言中,这种观察者模式通常需要单独实现,而在Objective-C中,通...

weixin_30892987的博客 223

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

【iOS KVO(下) KVO的内部结构和源码】

KVO 和 KVC 都是 Cocoa 框架中的重要机制,它们可以帮助开发者更加方便地操作对象的属性和状态。

weixin_61639290的博客 1271

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

KVO实现原理

KVO(key value observing)键值监听是我们在开发中常使用的用于监听特定对象属性值变化的方法,常用于监听数据模型的变化 KVO是为了监听一个对象的某个属性值是否发生变化。在属性值发生变化的时候,肯定会调用其setter方法。所以KVO的本质就是监听对象有没有调用被监听属性对应的setter方法 在学习实现原理之前我们首先先了解一下KVO常用的有哪些方法 KVO常用方法 /* 注册...

D_猿员的博客 766
上一篇: 人的能量
下一篇: iOS面试攻略之 — 详解Objective-C runtime
iteye_3205
博客等级 码龄8年 0粉丝 44原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值