KVO,一个并不陌生的名词,用法也简单如斯。

可是,你真的了解它么?这东西为什么会存在?是否了然其背后的逻辑?我们又该以何种姿态来使用才算是优雅呢?这就是本篇文章能给你的想要的了解。

官方说法

KVO 的实现官方并没有公开,我们能看到的是这样一段说明:工商银行

Key-Value Observing Implementation Details

Automatic key-value observin源码时代g is implemented using a tec指针是什么hniquGoe calledisa-swizzling.

Theisapointer, 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 theisapointer to determine class membership. Instead,源码中的图片 you should use theclassmethod 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 添加了监听,observerself(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 处,也就是这里:

让道听途说的KVO不再遥远

run 起来,我们在断点处及单步运行后,分别打印一下 onePersonisa

让道听途说的KVO不再遥远

很明显,isa 的指向确实发生了变化,在 addObserver 之后,onePersonisa 指向了一个叫 NS指针是什么KVONotifying_Person 的类,这个类并不是我们自己定义的,而是在我们执行 addObserver 之后才生成的,也就是通过 runtime 创建的一个类,且在其类名中还能找到 Person 的影子,确切的说就是加了一个前源码交易平台缀。

目前看来,确实如官方说法一样,生成了一个中间类,可是源码编辑器下载却并没有交代这个新生成的类是何许人,与原类的关系。看类名又似乎是有关联的。

倒是有个道听途说的说法:工龄越长退休金越多吗工龄越长退休金越多吗成的类是原类的子类

我们不妨加以验证一下,同样的手法看下其 superclass

让道听途说的KVO不再遥远

这里看到的都是 NSObject。那是不是说明上面提到的指针式万用表使用方法说法不成立呢?

其实不然,我们知道 superclass 拿到的可能并不是准确的,比如要是指针数学重写了呢?我们有个更准确的获取源码1688方式,同样的手法:

让道听途说的KVO不再遥远

可以看到,obj宫颈癌ect_getClass() 获取到的跟 isa 是一样的,如果查看 runtime 源码就会知道指针,其实际上就是返回的 isa。而从 class_getSuperclass() 的结果我们可以看到 NSGoKVONotifying_Person 的父类确实是 Person

总结来说,上面道听途说的说法还是靠谱的,新生成的类确实是原类的子类,在命名上其实就指针数组是在原类的类名上加上了前缀 NSKVONot指针ifying_

class

上面我们指针说漫通过 superclass 拿到的前后结果一致,从宫颈癌另一个方面正好是说明了 NSKVONotifying_Person 类中其实是重写了 superclass 方法实现的。

而我们从官方说法中提到的 class 了解到,我们通过 class 获取对象的类别实际上是更准确的。这里怎么理解呢?

不妨同样的手源码编辑器法,再来一次:

让道听途说的KVO不再遥远

可以看到,classsuperclas源码精灵永久兑换码s 的输出如出一辙宫颈癌。说明什么呢?说明 c源码网站lass 方法也是被重写了。

那么问题来了,这样看其实 c公积金lass 拿到的并不是准确的结果啊,为啥官方推荐呢?

有句话说的是,你看到的只是它想让你看到的,这也就是官方希望的。

指针万用表怎么读数句话说,官方其实并不想暴露 NSKVONotifying_Person 类的存在,只是为了实现 KVO 的一种手段,让外界看起来世界和平,而他自己干了他爱干的事儿。

所以,了然否?源码编程器

setter

我们还曾听说,KVO 的实现,最终是通过重写 setter 方法实现的。是不是呢?

来来来,手法不变,我们再来一次:

让道听途说的KVO不再遥远

我们依旧是在 ad指针式万用表dObserver 前后打印的相关信息,是 setter 方法的 IMP 地址及其相关信息。

不难看出在这前后,setter 方法的实工商银行现确实发生了变化,之前我们根据其信息能知道,其调用的是正常的 setAge: 方法,而之后呢?Foundation源码交易平台 _NSSetUnsignedLongLongValueAndNotify 是什么玩意儿呢?

至少能知道就是因为这个实现源码编辑器KVO 在值改变前后的通知操作,而其中 UnsignedLongLong 应该跟我们属性的数据类型有关,按说应该还有一堆类似的方法,我指针万用表读数图解们来确认一下。

我们把 age 的数据类型改一下看看,比如改成 double 之后:

让道听途说的KVO不再遥远

其关键字或者叫函数名也发成了变化,对应上了 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];
}

龚俊们去掉断点,再跑一下:

让道听途说的KVO不再遥远

我们很明显的可以看到源码交易平台,在添加监听之后触发 setAge: 的时候,先后调用了 PersonwillChangeValueForKey:didChangeValueForKey: 方法以及 ViewContrillerobserveValueForKeyPath:源码时代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 属性赋值,我们跑一下看看。

让道听途说的KVO不再遥远

首先来看,这里手动调用并没有什么问题,跟之前的日志也一模一样(其实还是有一点点差别,后面讲)。

但是,问题也就在一模一样,问题是: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 的,它源码就是出自 FBKVOController, 在这里。

也可以研究下其封装 KVO 的逻辑,也有不少收获,有机会我们后面再来聊一聊。

最后

ok,以上,希望我们都能有所收获。