当前位置: 首页 > 科技观察

iOS开发中KVO的内部实现2009年的一篇文章

时间:2023-03-12 17:07:20 科技观察

更深入的讲解了KVO的内部实现。KVO是实现CocoaBindings的基础,它提供了一种方式,当一个属性发生变化时,相应的对象将得到通知。在其他语言中,这种观察者模式通常需要单独实现,但在Objective-C中,通常不需要添加额外的代码就可以使用。概述这怎么可能?其实这都是通过Objective-C强大的运行时来实现的。当你第一次观察一个对象时,运行时会创建一个新的子类来继承原始类。在这个新类中,它重写了所有观察到的键,然后将对象的isa指针指向新创建的类(这个指针告诉Objective-C运行时一个对象是什么类型的对象)。所以object神奇地变成了新子类的一个实例。这些重写的方法实现了如何通知观察者。当一个key改变时,会触发setKey方法,但是这个方法被重写了,内部增加了一个通知机制。(当然也可以跳过setXXX方法,比如直接修改iVar,但是不推荐这样做)。有趣的事实:Apple不希望这种机制暴露给外界。除了setter之外,这个动态生成的子类还重写了-class方法,依然返回原来的类!如果不仔细看,KVOed对象看起来和原来的对象是一样的。在下面深入挖掘,看看这是如何实现的。我写了一个程序来演示KVO背后的机制。//gcc-okvoexplorer-frameworkFoundationkvoexplorer.m#import#import@interfaceTestClass:NSObject{intx;inty;intz;}@propertyintx;@propertyinty;@propertyintz;@end@implementationTestClass@synthesizex,y,z;@endstaticNSArray*ClassMethodNames(Classc){NSMutableArray*array=[NSMutableArrayarray];unsignedintmethodCount=0;Method*methodList=class_copyMethodList(c,&methodCount);unsignedinti;for(i=0;i",name,obj,class_getName([objclass]),class_getName(obj->isa),[ClassMethodNames(obj->isa)componentsJoinedByString:@","]];printf("%s\n",[strUTF8String]);}intmain(intargc,char**argv){[NSAutoreleasePoolnew];TestClass*x=[[TestClassalloc]init];TestClass*y=[[TestClassalloc]init];TestClass*xy=[[TestClassalloc]init];TestClass*control=[[TestClassalloc]init];[xaddObserver:xforKeyPath:@"x"options:0context:NULL];[xyaddObserver:xyforKeyPath:@"x"options:0context:NULL];[yaddObserver:yforKeyPath:@"y"options:0context:NULL];[xyaddObserver:xyforKeyPath:@“y”选项:0上下文:NULL];PrintDescription(@“control”,control);PrintDescription(@“x”,x);PrintDescription(@“y”,y);PrintDescription(@“xy”,xy);printf("UsingNSObjectmethods,normalsetX:is%p,overriddensetX:is%p\n",[controlmethodForSelector:@selector(setX:)],[xmethodForSelector:@selector(setX:)]);printf("Usinglibobjcfunctions,normalsetX:is%p,overriddensetX:is%p\n",method_getImplementation(class_getInstanceMethod(object_getClass(control),@selector(setX:))),method_getImplementation(class_getInstanceMethod(object_getClass(x)),@selector(setX:))));return0;}我们首先从头到尾定义了一个TestClass类,它有3个属性。然后定义一些方便的调试方法。ClassMethodNames使用Objective-C运行时方法来遍历类以获取方法列表。请注意,这些方法不包括父类方法。PrintDescription打印对象的所有信息,包括类信息(包括-class和运行时获取的类),以及该类实现的方法。然后创建了四个TestClass实例,每个实例使用不同的观察方法。x实例有一个观察者观察xkey,对于y也类似,xy。相比之下,zkey没有观察者。***控制实例没有任何观察者。然后打印出这4个对象的描述。然后继续打印重写的setter的内存地址和未重写的setter的内存地址进行比较。这样做了两次,因为-methodForSelector:未能获得重写的方法。KVO试图隐藏它实际上创建了一个新子类的事实!但是使用运行时方法会自行显示。运行代码以查看此代码控件的输出:NSObjectclassTestClasslibobjcclassTestClassimplementsmethodsx:NSObjectclassTestClasslibobjcclassNSKVONotifying_TestClassimplementsmethodsy:NSObjectclassTestClasslibobjcclassNSKVONotifying_TestClassimplementsmethodsxy:NSObjectclassTestClasslibobjcclassNSKVONotifying_TestClassimplementsmethodslookslikeaninternalmethod,yesFoundation使用nm-a得到一个完整的私有方法列表:0013df80t__NSSetBoolValueAndNotify000a0480t__NSSetCharValueAndNotify0013e120t__NSSetDoubleValueAndNotify0013e1f0t__NSSetFloatValueAndNotify000e3550t__NSSetIntValueAndNotify0013e390t__NSSetLongLongValueAndNotify0013e2c0t__NSSetLongValueAndNotify00089df0t__NSSetObjectValueAndNotify0013e6f0t__NSSetPointValueAndNotify0013e7d0t__NSSetRangeValueAndNotify0013e8b0t__NSSetRectValueAndNotify0013e550t__NSSetShortValueAndNotify0008ab20t__NSSetSizeValueAndNotify0013e050t__NSSetUnsignedCharValueAndNotify0009fcd0t__NSSetUnsignedIntValueAndNotify0013e470t__NSSetUnsignedLongLongValueAndNotify0009fc00t__NSSetUnsignedLongValueAndNotify0013e620t__NSSetUnsignedShortValueAndNotify这个列表也能发现一些有趣的东西。Forexample,Applehaswrittencorrespondingimplementationsforeachprimitivetype.Objective-C对象只会用到__NSSetObjectValueAndNotify,但是需要一整套来对应其余的,而且好像没有完全实现,比如longdobule或者_Bool。它甚至不提供通用指针类型的方法。因此,不在这个方法列表中的属性实际上是不支持KVO的。KVO是一个非常强大的工具,有时太强大了,尤其是它的自动触发通知机制。现在您知道了它的内部工作原理,这些知识可能会帮助您更好地使用它,或者在出错时更容易地调试它。如果你打算使用KVO,或许你可以看看我的另一篇文章Key-ValueObservingDoneRight【移动开发视频教程推荐】iOS培训Objective-C基础视频教程(40集)Cocos2d-x从零开始【5掌握跨平台游戏开发工具】(12集)ObjectiveC编程基础(24集)Android技术入门教程(12集)微信开放平台-Android应用接入(4集)Cocos2d-x跨平台游戏开发基础(29集)iOS开发视频教程-iOS网络编程[进阶](39集)移动应用用户体验设计进阶教程(60集)从零开始学习iOS开发-UI多视图(30集)iOS开发视频教程[基础]]入门]