KVO实现原理
2016-04-24 17:07
330 查看
概览
必须要mark一下的,要开始写自己的技术博客了,这是伟大的开始,希望能够给大家带来益处吧。更多的是给自己的技术做一个总结,在秋招的时候有个地方可以复习。我希望自己博客风格是能够深入浅出。慎重对待每一篇博客,既有常规的简单使用,又有深入的分析,源码之下,无所隐藏。
言归正传,本文分为两个大的方面。一、kvo的简单使用,常用场景。二、kvo的来龙去脉。
KVO 使用方法,和常用场景。
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.— Key-Value Observing Programming Guide
简而言之,kvo就是允许一个对象去监听其他对象(可以自己)指定属性的值的变化。但是一般涉及的类比较复杂的时候,我们应该该使用Notification或者delegate,不然太多关联,bug都不知道哪里找.我一般使用kvo在tabbarItem的badge属性,这样就可以在badge一改变,直接刷新角标的数字。
使用方法分3步:
1. 注册观察者 addObserver:forKeyPath:options:context: 2. 观察者中实现 observeValueForKeyPath:ofObject:change:context: 3. 移除观察者: removeObserver:forKeyPath:
注意:
注册与移除必须成对出现,否则会crash掉。
观察者实现的方法,change字典里存放的数据与你注册观察者时
的options相关,NSKeyValueObservingOptionNew表现为
改变后的值,键为@”new”;NSKeyValueObservingOptionOld,
同理键为@”old”,根据自己的需要选择。
kvo实现原理
1. runtime生成被监控类的子类NSKVONotifying_xx实例对 象,被监控对象的isa指针指向子类,真正的起作用的类就成了 子类。 2. 一旦被监控类的某个属性改变,就会在子类中重写相应的set方 法,在set方法中调用NSObject的- willChangeValueForKey: 和- didChangeValueForKey:通知观察者。自己可以测试在被 监控的类中自己重写这两个方法中的一个,可以看到观察者就 收不到 -observeValueForKeyPath:ofObject:change:context:消息 了,说明截断了消息,使得kvo机制不起作用了。 3. 子类中还重写了- class方法,返回父类的 class,欲盖弥彰, 就好像没有这个子类一样。 4.删除观察者后一切照旧,对象的isa指针重新指向父类。
下面通过代码来验证:
自定义Person类,有age和height两个属性。自己时被监控对象,为了简单起见,也是观察者。
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface Person : NSObject @property(nonatomic,assign) int age; @property(nonatomic,assign) float height; @end @implementation Person /** * 如果重写,这两个方法,kvo就失效了。 */ //- (void)willChangeValueForKey:(NSString *)key{ // NSLog(@"willChangeValueForKey"); //} //- (void)didChangeValueForKey:(NSString *)key{ // NSLog(@"didChangeValueForKey"); //} //options属性改变change的值,这个是观察者要实现的方法。 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"age"]) { NSLog(@"%@",change); } } @end
获取一个类实现的所有selector(不包括父类的方法)
static NSArray* classMethodsName(Class c){ NSMutableArray* array = [NSMutableArray new]; uint count = 0; Method* methods = class_copyMethodList(c, &count); for(int i=0; i<count; i++){ [array addObject:NSStringFromSelector( method_getName(methods[i]) ) ]; } return array; }
static void PrintDescription(NSString *name, id obj) { //重点关注,对象的类型,runtime的类型。 NSString *str = [NSString stringWithFormat: @"%@: %@\n\tNSObject class %@\n\tlibobjc class %@\n\timplements methods <%@>", name, obj, [obj class], object_getClass(obj), [ classMethodsName( object_getClass(obj) ) componentsJoinedByString:@","] ]; printf("%s\n", [str UTF8String]); }
main函数里,定义了三个人,one,two,three,one观察了自己的age属性,two观察了自己height属性,three作为对比。
int main(int argc, const char * argv[]) { @autoreleasepool { Person* one = [Person new]; Person* two = [Person new]; Person* three = [Person new]; printf("注册观察者之前\n"); PrintDescription(@"one", one); PrintDescription(@"two", two); PrintDescription(@"three", three); /** * 在注册观察者之前 */ //breakpint 1 [one addObserver:one forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; [two addObserver:two forKeyPath:@"height" options:NSKeyValueObservingOptionNew context:nil]; printf("注册观察者之后\n"); PrintDescription(@"one", one); PrintDescription(@"two", two); PrintDescription(@"three", three); //查看类的方法实现(函数指针)的地址。 printf("\none own method setAge:%p libSubcluss method setAge:%p\n", class_getMethodImplementation( [one class], @selector(setAge:) ), class_getMethodImplementation( object_getClass(one), @selector(setAge:) ) ); printf("two own method setHeight:%p libSubcluss method setHeight:%p\n" ,class_getMethodImplementation( [two class], @selector(setHeight:) ), class_getMethodImplementation( object_getClass(two), @selector(setHeight:) ) ); printf("three own method setHeight:%p three libSubcluss method setHeight:%p\n\n", class_getMethodImplementation( [three class], @selector(setHeight:) ), class_getMethodImplementation( object_getClass(three), @selector(setHeight:) ) ); // one.age = 14; // two.height = 5.5; /** * 注册观察者之后 */ //breakpoint 2 [one removeObserver:one forKeyPath:@"age"]; [two removeObserver:two forKeyPath:@"height"]; printf("删除观察者之后\n"); PrintDescription(@"one", one); PrintDescription(@"two", two); PrintDescription(@"three", three); //breakpoint 3 } return 0; }
breakpint 1
one: <Person: 0x1006001c0> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject:change :context:,height,setHeight:> two: <Person: 0x100600220> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject:change :context:,height,setHeight:> three: <Person: 0x100600230> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject:change :context:,height,setHeight:>
结论:
在未添加观察者之前,运行时的类和对象本身的类是一样的。
breakpoint2
one: <Person: 0x1006001c0> NSObject class Person libobjc class NSKVONotifying_Person implements methods <setHeight:,setAge:,class,dealloc,_isKVOA> two: <Person: 0x100600220> NSObject class Person libobjc class NSKVONotifying_Person implements methods <setHeight:,setAge:,class,dealloc,_isKVOA> three: <Person: 0x100600230> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject:change :context:,height,setHeight:> one own method setAge:0x1000015b0 libSubcluss method setAge:0x7fff8a5a1a81 two own method setHeight:0x100001600 libSubcluss method setHeight:0x7fff8a5a1ba9 three own method setHeight:0x100001600 three libSubcluss method setHeight:0x100001600
结论
1. 监听过的属性值都会在NSKVONotifying_XX(本例是Person) 生成对应的set方法。 2. 重写了class方法,目的在于隐藏子类,依然返回父类的class, 伪装自己。 3. one的setAge方法,two的setHeight方法,居然有两个实 现,说明运行时至少是该方法重写了。而没有监听属性的 three一切正常。 至此,应该算是比较明白runtime干了一件什么样的事了,还不会,那我们看看,删除监听的后效果。
breakpoint3
one: <Person: 0x1006001c0> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject:change :context:,height,setHeight:> two: <Person: 0x100600220> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject:change: context:,height,setHeight:> three: <Person: 0x100600230> NSObject class Person libobjc class Person implements methods <age,setAge:,observeValueForKeyPath:ofObject: change:context:,height,setHeight:>
一切都是原来的的样子,runtime的magic。
结论
只要监听了属性的改变,父类就通过isa-swizzle(isa混写),指向了子类,而狡猾的子类不仅完成了该有的set方法的重写,而且重写class方法,返回父类的类对象。然而runtime之下,无所隐藏.动态生成类和对象不是本文的主旨,会一一到来。
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- 浅析Ruby的源代码布局及其编程风格
- Java Runtime Environment 5.0 Update 12 下载
- asp.net 抓取网页源码三种实现方法
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- php set_magic_quotes_runtime() 函数过时解决方法
- Asp.Net 程序错误Runtime Error原因与解决
- 讲解iOS开发中基本的定位功能实现