KVO实现原理

news/2024/10/22 8:19:29/

概览

本文分为两个大的方面。一、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,b不然太过分散,bug不容易查找,当然delegate,通知也需要统一处理.现在使用属性监听的场景还是比较少了,我们这里主要是探究一下苹果的实现原理。

使用方法分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 
#import @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; iNSStringFromSelector(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:%plibSubcluss 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 Personlibobjc class Personimplements methods   setAge:,observeValueForKeyPath:ofObject:change:context:,height,setHeight:>
two: <Person: 0x100600220>NSObject class Personlibobjc class Personimplements methods setAge:,observeValueForKeyPath:ofObject:change:context:,height,setHeight:>
three: <Person: 0x100600230>NSObject class Personlibobjc class Personimplements methods setAge:,observeValueForKeyPath:ofObject:change:context:,height,setHeight:>

复制

结果1:

在未添加观察者之前,运行时的类和对象本身的类是一样的。

复制

breakpoint2

one: 0x1006001c0>NSObject class Personlibobjc class NSKVONotifying_Personimplements methods class,dealloc,_isKVOA>
two: 0x100600220>NSObject class Personlibobjc class NSKVONotifying_Personimplements methods class,dealloc,_isKVOA>
three: 0x100600230>NSObject class Personlibobjc class Personimplements methods 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

复制

结果2

1.  监听过的属性值都会在NSKVONotifying_XX(本例是Person)生成对应的set方法。
2. 重写了class方法,目的在于隐藏子类,依然返回父类的class,伪装自己。
3.  one的setAge方法,two的setHeight方法,居然有两个实现,说明运行时至少是该方法重写了。而没有监听属性的three一切正常。
至此,应该算是比较明白runtime干了一件什么样的事了,还不会,那我们看看,删除监听的后效果。

复制

breakpoint3

one: <Person: 0x1006001c0>NSObject class Personlibobjc class Personimplements methodssetAge:,observeValueForKeyPath:ofObject:change:context:,height,setHeight:>
two: <Person: 0x100600220>NSObject class Personlibobjc class Personimplements methods setAge:,observeValueForKeyPath:ofObject:change:context:,height,setHeight:>
three: <Person: 0x100600230>NSObject class Personlibobjc class Personimplements methods      setAge:,observeValueForKeyPath:ofObject:change:context:,height,setHeight:>

复制

一切都是原来的的样子,runtime的magic。


http://www.ppmy.cn/news/253530.html

相关文章

2022-2028全球与中国近场扫描光学显微镜(NSOM)市场现状及未来发展趋势

【报告篇幅】&#xff1a;89 【报告图表数】&#xff1a;127 【报告出版时间】&#xff1a;2021年12月 报告摘要 根据简乐尚博&#xff09;的统计及预测&#xff0c;2021年全球近场扫描光学显微镜&#xff08;NSOM&#xff09;市场销售额达到了0.6亿美元&#xff0c;预计2028…

投影仪-相机标定

1. 单目相机标定 引言 相机标定已经研究多年&#xff0c;标定的算法可以分为基于摄影测量的标定和自标定。其中&#xff0c;应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统&#xff0c…

全球与中国激光共焦扫描显微镜市场深度研究分析报告

【报告篇幅】&#xff1a;93 【报告图表数】&#xff1a;130 【报告出版时间】&#xff1a;2022年1月 2021年全球激光共焦扫描显微镜市场销售额达到了 亿美元&#xff0c;预计2028年将达到 亿美元&#xff0c;年复合增长率&#xff08;CAGR&#xff09;为 %&#xff08;2022…

2022年超声生物显微镜(UBM)市场深度分析及发展研究预测报告

本文研究全球市场、主要地区和主要国家超声生物显微镜(UBM)的销量、销售收入等&#xff0c;同时也重点分析全球范围内主要厂商&#xff08;品牌&#xff09;竞争态势&#xff0c;超声生物显微镜(UBM)销量、价格、收入和市场份额等。 针对过去五年&#xff08;2017-2021&#…

芯片里面长啥样?扫描电子显微镜放大10000倍告诉你!

说来惭愧&#xff0c;张大妈应该算是一个消费指导网站。但是笔者这篇文章基本并没有涉及消费&#xff0c;而更像是一篇科普文章。所以还请感兴趣的童鞋们继续围观&#xff0c;对芯片内部结构不感兴趣的童鞋们请返回。 最近521的第一届值友节上&#xff0c;看到张大妈的CEO放PPT…

高精度结构光工业3D相机Mech-Eye PRO全面升级:可选蓝光/白光版本,适合中距离应用...

近日&#xff0c;梅卡曼德正式发布第四代Mech-Eye PRO&#xff08;原Pro Enhanced系列&#xff09;高精度结构光工业3D相机。Mech-Eye PRO是梅卡曼德针对无序抓取、定位装配、学术研究等对精度要求较高的中距离应用场景而推出的旗舰产品。相较于上一代产品&#xff0c;第四代Me…

激光共聚焦显微镜原理

激光共聚焦显微镜原理 激光共聚焦扫描显微技术&#xff08;Confocal laser scanning microscopy&#xff09;是一种高分辨率的显微成像技术。普通的荧光光学显微镜在对较厚的标本&#xff08;例如细胞&#xff09;进行观察时&#xff0c;来自观察点邻近区域的荧光会对结构的分辨…

光学显微镜油镜的使用

光学显微镜油镜的使用 在显微镜中&#xff0c;更换物镜后不必转动粗调旋钮&#xff0c;只要稍稍调节细调旋钮就可以聚焦。并且由于干系物镜较长的工作距离&#xff0c;它们的使用不存在多大问题&#xff0c;但是对于浸润物镜使用中的一些问题&#xff0c;有必要加以简要的叙述。…