基本用法
创建一个iOS项目
新建类TestObj
1 | //TestObj.h |
ViewController如果要需要监听TestObj对象的name值的改变,代码如下
1 | #import "ViewController.h" |
运作模拟器点击后输出以下 很简单
1 | <TestObj: 0x6000024d4400>对象的name值改变了{ |
如何实现的当属性改变的时候方法会调用
在addObserver:forKeyPath:options:context:方法前后加入下面的打印
1 | NSLog(@"begin--%@",object_getClass(self.obj1)); |
输出self.obj1前后的具体是哪个对象的实例
1 | begin--TestObj |
由isKindOfClass输出的结果可以看出addObserver:forKeyPath:options:context:就会生成TestObj的子类名字为NSKVONotifying+_+TestObj;
谁调用了addObserver:forKeyPath:options:context:方法
TestObj增加一个方法
1 | //.h |
ViewController调用修改为下面的形式
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
发现没有任何打印,得出新生成的子类是重写父类的set方法从而达到选择性调用addObserver:forKeyPath:options:context:方法的
生成的子类NSKVONotifying+_+XXX的数据结构是怎样的
通过上面已经知道addObserver:forKeyPath:options:context:方法以后生成了一个新的对象,那么这个子类对象的元类对象和原来的类相同吗,修改代码如下看看结果
1 | - (void)viewDidLoad { |
输出
1 | 前-0x10ee87318--0x10ee87318--0x10ee87318--0x10ee87318 |
说明生成的新类对象的元类对象也变了,addObserver:forKeyPath:options:context:过后就变成了下面的这张图。

为什么[self.obj1 isMemberOfClass:[self.obj3 class]] == true?
读过runtime源码应该知道isMemberOfClass实现如下
1 | - (BOOL)isMemberOfClass:(Class)cls { |
也就是说isMemberOfClass获取的是对象的isa指针,那按照上面的这张图self.obj1调用isMemberOfClass应该是NSKVONotifying_TestObj,而self.obj3 class]应该是TestObj,怎么会相等呢
再修改下代码如下
1 | - (void)viewDidLoad { |
输出
1 | 方法名称:setName:--0x10f1e0e8a |
说明NSKVONotifying_TestObj里面有4个方法,重写了setName:、class、dealloc、_isKVOA
断点在此处

输出

我们可以分别得到具体的方法实现
1 | imp = 0x000000010af1ae8a (Foundation`_NSSetObjectValueAndNotify) |
对于class我们有理由猜测就是返回了
1 | - (Class)class { |
也就解释了为什么返回true了
具体值改变的时机
我们把代码恢复到下面的样子
1 | - (void)viewDidLoad { |
在TestObj.m中增加2个方法
1 | - (void)willChangeValueForKey:(NSString *)key { |
再运行点击输出
1 | willChangeValueForKey--begin |
得出2个结论:
NSKVONotifying_TestObj重写了set方法并且调用了willChangeValueForKey和didChangeValueForKey;- 真正改变的时机是
didChangeValueForKey方法,因为didChangeValueForKey--end之前值已经发生了改变
如何在不调用set方法的情况下主动触发KVO?
由前面我们知道直接通过以下方式是不会触发KVO的
1 | - (void)updateWithName:(NSString *)name { |
要想这种方式也会触发KVO监听需要改造下代码
1 | - (void)updateWithName:(NSString *)name { |
这样就可以了
willChangeValueForKey必须要调用的,读者可以自己试试看不加会不会触发到监听