`
miss大为
  • 浏览: 82570 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

(译)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.     int x; 
  9.     int y; 
  10.     int z; 
  11. @property int x; 
  12. @property int y; 
  13. @property int z; 
  14. @end 
  15.  
  16. @implementation TestClass 
  17. @synthesize x, y, z; 
  18. @end 
  19.  
  20. static NSArray *ClassMethodNames(Class c) 
  21.     NSMutableArray *array = [NSMutableArray array]; 
  22.      
  23.     unsigned int methodCount = 0; 
  24.     Method *methodList = class_copyMethodList(c, &methodCount); 
  25.     unsigned int i; 
  26.     for(i = 0; i < methodCount; i++) 
  27.         [array addObject: NSStringFromSelector(method_getName(methodList[i]))]; 
  28.     free(methodList); 
  29.      
  30.     return array; 
  31.  
  32. static void PrintDescription(NSString *name, id obj) 
  33.     NSString *str = [NSString stringWithFormat: 
  34.         @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@>"
  35.         name, 
  36.         obj, 
  37.         class_getName([obj class]), 
  38.         class_getName(obj->isa), 
  39.         [ClassMethodNames(obj->isa) componentsJoinedByString:@", "]]; 
  40.     printf("%s\n", [str UTF8String]); 
  41.  
  42. int main(int argc, char **argv) 
  43.     [NSAutoreleasePool new]; 
  44.      
  45.     TestClass *x = [[TestClass alloc] init]; 
  46.     TestClass *y = [[TestClass alloc] init]; 
  47.     TestClass *xy = [[TestClass alloc] init]; 
  48.     TestClass *control = [[TestClass alloc] init]; 
  49.      
  50.     [x addObserver:x forKeyPath:@"x" options:0 context:NULL]; 
  51.     [xy addObserver:xy forKeyPath:@"x" options:0 context:NULL]; 
  52.     [y addObserver:y forKeyPath:@"y" options:0 context:NULL]; 
  53.     [xy addObserver:xy forKeyPath:@"y" options:0 context:NULL]; 
  54.      
  55.     PrintDescription(@"control", control); 
  56.     PrintDescription(@"x", x); 
  57.     PrintDescription(@"y", y); 
  58.     PrintDescription(@"xy", xy); 
  59.      
  60.     printf("Using NSObject methods, normal setX: is %p, overridden setX: is %p\n"
  61.           [control methodForSelector:@selector(setX:)], 
  62.           [x methodForSelector:@selector(setX:)]); 
  63.     printf("Using libobjc functions, normal setX: is %p, overridden setX: is %p\n"
  64.           method_getImplementation(class_getInstanceMethod(object_getClass(control), 
  65.                                    @selector(setX:))), 
  66.           method_getImplementation(class_getInstanceMethod(object_getClass(x), 
  67.                                    @selector(setX:)))); 
  68.      
  69.     return 0; 
 
我们从头到尾细细看来。
 
首先定义了一个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
分享到:
评论

相关推荐

    ios KVO实现原理

    ### KVO的内部实现 KVO的底层实现依赖于Objective-C的运行时系统。它通过动态地创建被观察对象的类别(Category)并在其中添加方法来实现。当属性被设置时,这些动态添加的方法会捕获属性的改变,并通知观察者。 1...

    KVO实现原理demo

    **KVO(Key-ValueObserving)实现原理详解** KVO,全称为Key-ValueObserving,是Objective-C中一种强大的数据绑定机制,允许对象监听并自动响应其他对象属性值的变化。KVO是Apple在Foundation框架中提供的一个API,...

    ios-KVO底层实现--利用runtime简单的实现KVO底层原理.zip

    - 对于不可变对象(如`NSString`、`NSNumber`),KVO可能无法检测到内部值的改变,因为它们不会触发setter。 - 移除KVO时需确保在对象释放前执行,否则可能导致内存泄漏。 通过理解KVO的底层实现,开发者可以更好...

    KVO实现例子

    在iOS和Mac OS X开发中,KVO被广泛用于实现数据绑定和响应式编程,帮助开发者在对象的属性值发生变化时自动更新UI或其他依赖于该属性的对象。 KVO的基础是Apple的Objective-C runtime,它提供了动态添加观察者和...

    KVO实现的demo

    在iOS开发中,KVO是实现数据绑定和响应式编程的重要手段,尤其在视图与模型之间的数据同步方面。** ### KVO的基本原理 KVO基于Objective-C的动态性,通过向对象添加观察者来监听特定属性的变化。当被观察的属性值...

    iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式).zip

    iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式).zip,iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式)

    ios-MVC KVO实现.zip

    本项目"ios-MVC KVO实现.zip"似乎是一个关于如何在iOS应用中结合MVC模式和KVO技术的示例。下面将详细讲解MVC模式和KVO的原理以及它们在实际开发中的应用。 **MVC模式详解** MVC模式由模型(Model)、视图(View)...

    KVO机制工作原理

    同时,KVO维护了一个内部的观察者列表,用于存储所有的观察者。 ### KVO的注意事项 1. **手动管理观察者**:当你不再需要监听某个属性时,必须调用`-[NSObject removeObserver:forKeyPath:]`移除观察者,以避免...

    KVO的使用场景二 BBBB

    在iOS开发中,KVO是实现数据绑定和响应式编程的重要工具。在这个特定的使用场景中,我们将讨论如何利用KVO来实现在TableViewCell中的动态显示更新。 首先,让我们理解KVO的工作原理。当一个对象注册为另一个对象...

    iOS利用Runtime实现KVO

    KVO-----当对象某个属性(例如 demo 中的字符串 name)发生更改时,对象会获得通知,并作出相应处理,本demo用Runtime实现KVO原理。

    iOS KVO 监听frame

    - 如果手动设置了`willChangeValue(forKey:)`和`didChangeValue(forKey:)`,那么在这些方法内部进行的属性修改不会触发KVO通知,除非你在设置属性时也调用了`willChangeValue(forKey:)`和`didChangeValue(forKey:)`...

    IOS KVC和KVO

    4. 自动KVO:Objective-C中的`@dynamic`属性声明和`NSManagedObject`的属性默认支持KVO,但手动实现的属性需手动实现`willChangeValue(forKey:)`和`didChangeValue(forKey:)`方法。 总之,KVC和KVO为iOS开发者提供...

    iOSkvo的一个例子

    **iOS KVO(Key-Value Observing)详解** ...然而,由于KVO内部的机制,它可能会导致一些难以调试的问题,因此在使用时需要注意其潜在的风险,适时地使用其他通知机制如`NSNotification`或`Delegation`作为替代。

    kvo的ios代码

    在iOS中,KVO通常用于当某个对象的属性改变时,自动通知其他对象,从而实现数据绑定或者状态更新。 ### KVO的基本原理 KVO基于Objective-C的动态特性,通过改变对象的isa指针,将被观察对象的属性访问转发到一个...

    iOS之KVO实例代码

    利用KVO,当UIButton属性改变时,实现方法 Kvo是Cocoa的一个重要机制,他提供了观察某一属性变化的方法,极大的简化了代码。这种观察-被观察模型适用于这样的情况,比方说根据A(数 据类)的某个属性值变化,B...

    ios kvo 实战解析

    KVO是Apple在Foundation框架中实现的一种面向对象的通知机制,对于数据模型和视图之间的数据绑定非常有用。本文将深入探讨KVO的工作原理、使用方法以及实战示例。 ### 1. KVO的基本概念 - **观察者(Observer)**:...

    项目中使用KVO

    **KVO,即Key-Value Observing,是Objective-C中的一种设计模式,用于实现对象属性的监听和响应机制。在iOS开发中,KVO经常被用来实时监控某个对象的属性变化,进而执行相应的操作。本项目中使用KVO的目的是为了深入...

    swift-一句话使用KVO使用完无需自己移除KVO

    在Swift编程中,Key-Value Observing(KVO)是一种观察者模式的实现,用于监听对象属性的变化。KVO在Objective-C中广泛使用,并且在Swift中也可以通过桥接头文件来实现。标题提到的“swift-一句话使用KVO使用完无需...

    KVO_App:iOS Objective-C中KVC和KVO的基本实现

    Objective-C的`@dynamic`关键字可以指示编译器自动为属性实现KVO,前提是遵循`NSKeyValueObserving`协议并实现所需的方法。例如: ```objc @dynamic propertyName; ``` ### 5. 自定义KVO行为 为了提供自定义的KVO...

    在Swift中使用KVO的细节以及内部实现解析(推荐)

    为了深入理解Swift中使用KVO的细节以及其内部实现机制,我们需要先从几个基础概念开始。 首先,Swift与Objective-C的兼容性是一个不可忽视的话题。Swift作为苹果推出的现代编程语言,虽然提供了与Objective-C交互的...

Global site tag (gtag.js) - Google Analytics