OC-KVO的本质分析

本文深入探讨了KVO(Key-Value Observing)的工作原理,包括如何监听对象属性的变化,为何仅部分对象触发监听回调,以及KVO在运行时如何动态创建子类以实现观察者模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。

1.KVO的使用

代码如下:

@interface Person : NSObject

@property (nonatomic, assign) int age;

@end

----使用----

@interface ViewController ()

@property (nonatomic, strong) Person *p1;
@property (nonatomic, strong) Person *p2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.p1 = [[Person alloc] init];
    self.p1.age = 1;
    
    self.p2 = [[Person alloc] init];
    self.p2.age = 2;
    
    //给对象p1的age添加监听
    NSKeyValueObservingOptions option = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
    [self.p1 addObserver:self forKeyPath:@"age" options:option context:nil];
}

//修改属性的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.p1.age = 10;
    self.p2.age = 20;
}

//监听的回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"object = %@, keyPath = %@ change = %@", object, keyPath, change);
}

打印的结果如下:

可知,监听到了p1对象中age值的改变。

2.疑问

从上面的那段代码中,p1 和 p2都设置了age的值,相当于调用了setAge:这个方法,而setAge: 这个方法是实现是一样。那么为什么p1的setAge:这个方法执行完后会调用了监听的回调,而p2调用了setAge:的方法不会有监听的回调?

要解决这个疑问,我们需要去了解一下p1 和 p2的 类型,打个断点,查看p1 和 p2的isa指针类型如下:

打印的结果可以看出,p1的isa指针指向的不是Person的类对象,而是NSKVONotify_Person的类对象。也就是说,OC在运行时动态地创建了一个NSKVONotify_Person类,而这个类是继承于Person。

一般Person的对象,其内存布局如下:

但是添加了监听后的Person对象,其内存布局就变成了如下:

当p1调用setAge的方法时,不是直接调用Person类对象中的setAge:这个方法,而是先执行NSKVONotifying_Person类对象中的setAge:这个方法,这个方法调用的是Foundation中的_NSSetIntValueAndNotify这个方法。_NSSetIntValueAndNotify这个实现的逻辑大概如下:

(1)调用willChangeValueForKey: 通知开始用修改key的值了

(2)用super调用父类的setAge:的方法,即调用了Person的setAge:的方法

(3)调用didChangeValueForKey: 通知已经修改完key的值了。

3.验证

可以通过methodForSelector:这个方法来获取setAge:的地址,通过地址查看setAge:调用了哪个方法:

NSLog(@"%p %p", [self.p1 methodForSelector:@selector(setAge:)], [self.p2 methodForSelector:@selector(setAge:)] );

在命令行中用p (IMP)xxx地址,查看:

4.查看子类的方法

NSKVONotifying_Person除了重新了setAge:方法外,还有哪些方法呢?我们可以通过runtime的代码来获取:

- (void)getClassMethod:(Class)cls {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableArray *methodArray = [NSMutableArray array];
    for (int i = 0; i < count; i++) {
        Method met = methodList[i];
        NSString *methodName = NSStringFromSelector(method_getName(met));
        [methodArray addObject:methodName];
    }
    NSLog(@"%@--%@", cls, methodArray);
    free(methodList);
}

-------调用如下:
[self getClassMethod:object_getClass(self.p1)];
[self getClassMethod:object_getClass(self.p2)];

查看结果如下:

runtime除了获取方法外,还提供了其他的接口获取其他的信息,如下图:

5.总结

注意:

如果直接修改成员变量是不会调用KVO的,因为kvo的本质是setter方法触发的。但是手动调用willChangeValueForKey和didChangeValueForKey这两个方法也会触发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值