KVO,一个并不陌生的名词,用法也简单如斯。
可是,你真的了解它么?这东西为什么会存在?是否了然其背后的逻辑?我们又该以何种姿态来使用才算是优雅呢?这就是本篇文章能给你的想要的了解。
官方说法
KVO 的实现官方并没有公开,我们能看到的是这样一段说明:工商银行
Key-Value Observing Implementation Details
Automatic key-value observin源码时代g is implemented using a tec指针是什么hniquGoe calledisa-swizzling.
The
isa
pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contai宫颈癌ns pointers to the metho工资超过5000怎么扣税ds the class i指针式万用表mp源码之家lements, among other data.When an obse源码1688rver is reg指针istered for an attribute of an object the isa pointer of the observed object is mod枸杞ified, point指针数学ing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the指针数学 instance.
You shoulgoogled never rely on the
isa
pointer to determine class membership. Instead,源码中的图片 you should use theclass
method to determine the class of an object instance.
翻译过来大概是这样:
Key-Value Observing 实施细节
Automatic key-value observing 是使用称为 isa-swizzling 的技术实现的。
顾名思义,isa指针指向维护分派表的对象类。这个分派表本质上包含指向类实现的方法的指针,以及其他数据。
当一个观察者注册一个对象的属性时,被观察对象的isa指针会被修改,指向一个中间类,而不是真正的类。因此,isa指针的值不一定反映实例的实际类。
永远不要依赖isa指针来确定类成员身份。相反,应该使用class方法来确定对象实例的类。
总结来看,大概表达了这源码精灵永久兑换码么个意思:
-
KVO
全称叫key-value observing
; -
KVO
是用一种叫is指针c语言a-swizzling
的技术实现的,我们知不知道这个技术是怎么实现的,但是我们知道工龄差一年工资差多少他能实现什么,下面进一步解释就是他的作用; - 这技术直接把
isa
指针给改了,然后指向了一个中间类,而不是对象本身对应的类; - 既然
isa
指针能给改了,那么用isa
来作为他的身份证就不准了,关上门之后再开个窗,用class
方法来确定起身份。
我们不妨从官方说法开始验证工商银行一番。
基本操作
我们在使用 KVO
的时候,通常可以源码编程器这样指针式万用表使用方法操作:
Person * onePerson = [[Person alloc] init];
// 尝试更改 age 的值
onePerson.age = 1;
// self 监听 onePerson 的 age属性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[onePerson addObserver:self forKeyPath:@"age" options:options context:nil];
// 再次尝试更改 age 的值
onePerson.age = 10;
[onePerson removeObserver:self forKeyPath:@"age"];
上面的代码我们加在 Vi枸杞ewController
合适的地方,工资超过5000怎么扣税显然,我们对 Person
类的对象 onePerson
针对属性 age
添加了监听,observer
是 self
(ViewController
)。
然后,我们可以接着实现一下监听回调方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"Got!!对象 %@ 属性 %@ 发生了改变 :%@", object, keyPath, change);
}
ok,准备工作就绪,下面开始表演。
isa 指向了谁?
按照惯例,甩手就是一个断点,断在哪里呢?因为我们需要了解在添加 KVO
前后源码编程器的变化,所源码编辑器下载以断电加在 addO源码交易平台bserver
处,也就是这里:
run 起来,我们在断点处及单步运行后,分别打印一下 onePerson
的 isa
:
很明显,isa
的指向确实发生了变化,在 addObserver
之后,onePerson
的 isa
指向了一个叫 NS指针是什么KVONotifying_Person
的类,这个类并不是我们自己定义的,而是在我们执行 addObserver
之后才生成的,也就是通过 runtime
创建的一个类,且在其类名中还能找到 Person
的影子,确切的说就是加了一个前源码交易平台缀。
目前看来,确实如官方说法一样,生成了一个中间类,可是源码编辑器下载却并没有交代这个新生成的类是何许人,与原类的关系。看类名又似乎是有关联的。
倒是有个道听途说的说法:新工龄越长退休金越多吗生工龄越长退休金越多吗成的类是原类的子类。
我们不妨加以验证一下,同样的手法看下其 superclass
:
这里看到的都是 NSObject
。那是不是说明上面提到的子指针式万用表使用方法类说法不成立呢?
其实不然,我们知道 superclass
拿到的可能并不是准确的,比如要是指针数学重写了呢?我们有个更准确的获取源码1688方式,同样的手法:
可以看到,obj宫颈癌ect_getClass()
获取到的跟 isa
是一样的,如果查看 runtime
源码就会知道指针,其实际上就是返回的 isa
。而从 class_getSuperclass()
的结果我们可以看到 NSGoKVONotifying_Person
的父类确实是 Person
。
总结来说,上面道听途说的说法还是靠谱的,新生成的类确实是原类的子类,在命名上其实就指针数组是在原类的类名上加上了前缀 NSKVONot指针ifying_
。
class
上面我们指针说漫通过 superclass
拿到的前后结果一致,从宫颈癌另一个方面正好是说明了 NSKVONotifying_Person
类中其实是重写了 superclass
方法实现的。
而我们从官方说法中提到的 class
了解到,我们通过 class
获取对象的类别实际上是更准确的。这里怎么理解呢?
不妨同样的手源码编辑器法,再来一次:
可以看到,class
跟 superclas源码精灵永久兑换码s
的输出如出一辙宫颈癌。说明什么呢?说明 c源码网站lass
方法也是被重写了。
那么问题来了,这样看其实 c公积金lass
拿到的并不是准确的结果啊,为啥官方推荐呢?
有句话说的是,你看到的只是它想让你看到的,这也就是官方希望的。
换指针万用表怎么读数句话说,官方其实并不想暴露 NSKVONotifying_Person
类的存在,只是为了实现 KVO
的一种手段,让外界看起来世界和平,而他自己干了他爱干的事儿。
所以,了然否?源码编程器
setter
我们还曾听说,KVO
的实现,最终是通过重写 setter
方法实现的。是不是呢?
来来来,手法不变,我们再来一次:
我们依旧是在 ad指针式万用表dObserver
前后打印的相关信息,是 setter
方法的 IMP
地址及其相关信息。
不难看出在这前后,setter
方法的实工商银行现确实发生了变化,之前我们根据其信息能知道,其调用的是正常的 setAge:
方法,而之后呢?Foundation源码交易平台 _NSSetUnsignedLongLongValueAndNotify
是什么玩意儿呢?
至少能知道就是因为这个实现源码编辑器了 KVO
在值改变前后的通知操作,而其中 UnsignedLongLong
应该跟我们属性的数据类型有关,按说应该还有一堆类似的方法,我指针万用表读数图解们来确认一下。
我们把 age
的数据类型改一下看看,比如改成 double
之后:
其关键字或者叫函数名也发成了变化,对应上了 doub指针式万用表使用方法le
,可以猜测 Found公司让员工下班发手机电量截图ation
内部确实有一堆对应各种数据类型的函数。有说通过 nm
命令能查出来的,反正我这在最新的 SDK 中没有查到,但是并没有啥子影响。
肯定的是,这些个函数都做了同一个事儿,什么事儿呢?就是在 setAge
的同时,也在设置值的前后进行了通知。
我们通常这样,在Person
类中加上下面的代码:
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"Got!!Will change value for key :%@", key);
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"Got!!Did change value for key :%@", key);
[super didChangeValueForKey:key];
}
我龚俊们去掉断点,再跑一下:
我们很明显的可以看到源码交易平台,在添加监听之后触发 setAge:
的时候,先后调用了 Person
的 willChangeValueForKey:
,didChangeValueForKey:
方法以及 ViewContriller
的observeValueForKeyPath:源码时代ofObject:change:context:
方法。
所以,KVO
确实是重写了 setter
方法,其目的就是为了在改变属性值的前后通过 willChangeValueForKey:
,didChangeValueForKey:
实现通知,及触发 observeVa龚俊lueForKeyPath:ofObject:cha源码编辑器下载nge:context:
的回调。
手动触发 KVO
既然上面提到了 willChangeValueForKey:
,didC源码时代hangeValueFo源码编程器rKey:
,那我们自己调用会有什么后果呢?因为官方并没有限制我们对其调用,比如把代码改成下面这样:
// self 监听 p1的 age属性
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[onePerson addObserver:self forKeyPath:@"age" options:options context:nil];
// 暂时注释通过 setter 更改 age 的值
// onePerson.age = 10;
// 手动调用
[onePerson willChangeValueForKey:@"age"];
[onePerson didChangeValueForKey:@"age"];
这里我特意注释掉了通过源码编程器 setter
方法对 age
属性赋值,我们跑一下看看。
首先来看,这里手动调用并没有什么问题,跟之前的日志也一模一样(其实还是有一点点差别,后面讲)。
但是,问题也就在一模一样,问题是:observeValueForK指针式万用表使用方法eyPath:ofObject:change:context:
怎么触发的?
进一步注释掉 willChangeValueForKey:工商银行
就能确认,其就是 didChangeValueForK工商银行ey:
方法触发的,也就是说 KVO
重写 setter
之后,并不是在重写的 setter
方法内部触发 ob指针万用表读数图解serveValue龚俊ForKeyPa宫颈癌th:ofObject:change:conte指针万用表读数图解xt:
的回调,触发实际上发生在 didChangeValueForKey:
内部。
所以虽然我们可以手动调用上述的方法,但是还是需要谨慎,因为其会触发 observeValueForKeyPath:ofObject:change:context:
的回调,实际上就算是触发可能也没关系,关键是你瞅瞅回调之后的日志,其值前后其实并没有变源码交易平台化啊源码精灵永久兑换码(就是这里不一样),这可能就是问题了指针万用表怎么读数,这其实就是一次错误的触发了。
KVO 可能的实现
到这里,我们其实可以大致了解 KVO
的内部实现了,类似下面这样(只是伪代码)指针式万用表使用方法:
- (void)setAge:(NSUInteger)age {
// 这里就该是根据类型调用不同的函数了,这里只看了 age
_NSSetUnsignedLongLongValueAndNotify()
}
- (void)willChangeValueForKey:(NSString *)key {
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key {
[super didChangeValueForKey:key];
/// 触发回调
[observer observeValueForKeyPath:key ofObject:xxx change:xxx context:xxx];
}
void _NSSetObjectValueAndNotify() {
[self willChangeValueForKey:@"age"];
// 调用 Person 的 setter 方法
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
优雅的使用 KVO
上面我们说到,在添加了 KVO
的属性中,在修改属性值后会毁掉方法 observeValueForKeyPath:ofO指针数组和数组指针的区别bject:change:context:
,那我们在添加多个这样的指针数组和数组指针的区别属性之后google呢?我们就需要在上面回调方法中增加一堆 if-else
的判断了,这样显然并不优雅。
这里实际上是有一优雅的姿势来使用 KVO
的,它源码就是出自 FB
的 KVOController
, 在这里。
也可以研究下其封装 KVO
的逻辑,也有不少收获,有机会我们后面再来聊一聊。
最后
ok,以上,希望我们都能有所收获。