解析KVO实现原理

KVO的全称是Key-Value Observing,它实现了一种机制,对所关心的属性对象添加观察者,当属性值发生变化时会得到通知,我们可以对变化做相应的处理。看过设计模式的同学应该知道,这是一种典型的观察者模式。

KVO的最大优点就是底层框架已经支持,开发人员不需要实现属性值发生变化时发送通知的方案,这样就大大减少开发的工作量。其次,KVO框架很强大,可以支持多个观察者观察同一属性,或者一个观察者监听不同属性。

KVO的使用比较简单,基本上都是三步:

1.注册观察者

addObserver:forKeyPath:options:context:

2.观察者中实现

observeValueForKeyPath:ofObject:change:context:

3.移除观察者

removeObserver:forKeyPath:

使用方法比较简单,不懂的地方可以查阅sdk,接下来我们看看KVO的实现原理,这需要大家对Objective-C的对象模型有一定了解。如不了解,请参考之前分享的文章,苹果iOS开发深入浅出Cocoa之类与对象

研究KVO的时候我们发现系统使用Objective-C 强大的runtime功能实现了这个功能。属性类class中并没有实现KVO通知的相关方案,而是在调用addObserver之后定义属性类的子类subclass,subclass里边实现了属性的setter方法,setter方法中实现发动通知的功能。然后subclass中实现class函数,还让返回属性类的class,再让属性类对象的isa指向subclass,这样就伪装成表面上看还是属性类自己实现的通知功能。通过原理我们可以看出, 必须使用属性方法或者setValue:forKey方法赋值才会发送通知,直接赋值是不会收到通知的。

接下来我们写个演示程序看看KVO是怎么实现的:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @interface ClassTest : NSObject  
  2. {  
  3.     int x;  
  4.     int y;  
  5.     int z;  
  6. }  
  7.   
  8. @property (nonatomic, assign) int x;  
  9. @property (nonatomic, assign) int y;  
  10. @property (nonatomic, assign) int z;  
  11. @end  
  12. @implementation ClassTest  
  13. @synthesize x, y,z;  
  14.   
  15. -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context  
  16. {  
  17.     if([keyPath isEqualToString:@"x"])  
  18.     {  
  19.         NSObject* new = [change objectForKey:@"new"];  
  20.         NSLog(@"new x is %@", new);  
  21.     }  
  22.     else  
  23.     {  
  24.         [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];  
  25.     }  
  26. }  
  27. @end  

定义ClassTest类,定义三个属性x, y, z。

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. static NSArray* classMethodList(Class c)  
  2. {  
  3.     NSMutableArray* array = [NSMutableArray arrayWithCapacity:5];  
  4.     unsigned int count = 0;  
  5.     Method* methodList = class_copyMethodList(c, &count);  
  6.     for(int i = 0; i < count; ++i)  
  7.     {  
  8.         SEL sel = method_getName(*(methodList+i));  
  9.         [array addObject:NSStringFromSelector(sel)];  
  10.     }  
  11.     free(methodList);  
  12.     return array;  
  13. }  

定义classMethodList函数使用Objective-C runtime函数遍历class,获得方法列表

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. static void printDescription(NSString* name, id obj)  
  2. {  
  3.     NSString* string = [NSString stringWithFormat:@"%@:%@\n\tclass %@\n\tobjclass %@\n\timplementmethod %@\n",  
  4.                         name,  
  5.                         obj,  
  6.                         [obj class],  
  7.                         object_getClass(obj),  
  8.                         [classMethodList(object_getClass(obj)) componentsJoinedByString:@" , "]];  
  9.     printf("%s", [string UTF8String]);  
  10. }  

定义printDescription函数打印对象的所有信息,包括函数class信息和运行时动态class信息,注意这里object_getClass(obj)和obj->isa是等价的,只不过Objective-C 2.0开始不支持直接调用isa。

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int main(int argc, char * argv[])  
  2. {  
  3.     @autoreleasepool {  
  4.         ClassTest* x = [[ClassTest alloc] init];  
  5.         ClassTest* y = [[ClassTest alloc] init];  
  6.         ClassTest* xy = [[ClassTest alloc] init];  
  7.         ClassTest* control = [[ClassTest alloc] init];  
  8.           
  9.         [x addObserver:x forKeyPath:@"x" options:NSKeyValueObservingOptionNew context:nil];  
  10.         [y addObserver:y forKeyPath:@"y" options:NSKeyValueObservingOptionNew context:nil];  
  11.         [xy addObserver:xy forKeyPath:@"x" options:NSKeyValueObservingOptionNew context:nil];  
  12.         [xy addObserver:xy forKeyPath:@"y" options:NSKeyValueObservingOptionNew context:nil];  
  13.           
  14.         printDescription(@"x", x);  
  15.         printDescription(@"y", y);  
  16.         printDescription(@"xy", xy);  
  17.         printDescription(@"control", control);  
  18.           
  19.         printf("Using NSObject method, normal setX is %p, overrite setX is %p\n", [control methodForSelector:@selector(setX:)], [x methodForSelector:@selector(setX:)]);  
  20.         printf("Using libobjc method, normal setX is %p, overrite setX is %p\n",  
  21.                class_getMethodImplementation(object_getClass(control), @selector(setX:)),  
  22.                class_getMethodImplementation(object_getClass(x), @selector(setX:)));  
  23.           
  24.         return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));  
  25.     }  
  26. }  

我们在main函数中定义ClassTest对象x, y, z, control,x添加对属性x的观察者,y添加对属性y的观察者,xy添加对属性x和属性y的观察者,control不添加任何观察者,然后通过printDescription打印对象的信息,最后打印setX函数的地址,我们看看最终的打印结果。

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. x:<ClassTest: 0x8951690>  
  2.     class ClassTest  
  3.     objclass NSKVONotifying_ClassTest  
  4.     implementmethod setY: , setX: , class , dealloc , _isKVOA  
  5. y:<ClassTest: 0x89516c0>  
  6.     class ClassTest  
  7.     objclass NSKVONotifying_ClassTest  
  8.     implementmethod setY: , setX: , class , dealloc , _isKVOA  
  9. xy:<ClassTest: 0x89516d0>  
  10.     class ClassTest  
  11.     objclass NSKVONotifying_ClassTest  
  12.     implementmethod setY: , setX: , class , dealloc , _isKVOA  
  13. control:<ClassTest: 0x89516e0>  
  14.     class ClassTest  
  15.     objclass ClassTest  
  16.     implementmethod z , x , setX: , y , setY: , setZ: , observeValueForKeyPath:ofObject:change:context:  
  17. Using NSObject method, normal setX is 0x4ae0, overrite setX is 0x1134526  
  18. Using libobjc method, normal setX is 0x4ae0, overrite setX is 0x1134526  

从打印结果我们看到,实际上系统定了一个叫做 NSKVONotifying_ClassTest的子类,子类中实现了

setY: , setX: , class , dealloc , _isKVOA函数,这个_isKVOA函数应该是个私有函数,用来判断是否kvo框架生成的类,x, y, xy对象的运行时类都指向NSKVONotifying_ClassTest,通过class函数返回的类还是指向ClassTest,但是control对象的不管运行时类还是class函数返回的类都指向ClassTest。这样就验证了系统是通过定义Classtest类的子类来实现属性方法发送通知的,系统很聪明,子类中并没有实现setZ方法,因为我们并没有对属性z添加观察者。

在看看最后两行打印的结果,control对象的setX函数地址和x对象的setX函数地址是不一样的,说明setX函数被重写了。看别人之前的文章,通过NSObject方法打印control和x的setX函数地址是一样的,现在验证的结果地址却不一样,和使用runtime方法打印的结果完全一致,这个估计是新的系统底层做了修改,让使用NSObject的methodForSelector方法获得函数是子类的函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值