iOS底层学习——KVO

使用

/// 创建Person类
@interface DDPerson : NSObject
@property (nonatomic, assign) int age;
@end
#import "ViewController.h"
#import "DDPerson.h"
@interface ViewController ()
@property (nonatomic, strong) DDPerson *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    self.person = [[DDPerson alloc] init];
    self.person.age = 10;
    /// 添加KVO
    NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"age" options:option context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@ -- %@", keyPath, change);
    /* 打印结果
    age -- {
    	kind = 1;
    	new = 20;
    	old = 10;
	}
    */
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person.age = 20;
}
- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"age"];
}
@end

底层原理

当我们创建给类的属性添加KVO时,程序是如何执行的呢,接下来我们一起探讨下。

/// 首先我们创建Person的两个实例对象
Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];

// 我们给person1添加KVO
NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[person1 addObserver:self forKeyPath:@"age" options:option context:nil];
/// 如果我们同时修改person1和person2的age值
person1.age = 20;
person2.age = 20;

如果我们运行程序的话可以发现,person1监听到了age改变,person2没有监听到改变。我们知道,实例对象的isa指向的是类对象,但上方两个对象都是对age属性进行了修改,那么为什么没有走监听呢,带着这个疑问我们尝试打印一下两个实例对象的类对象。如下

// 接下来我们打印一下两个实例对象的类对象
NSLog(@"person1 ---------- %@", object_getClass(self.person1));
NSLog(@"person2 ---------- %@", object_getClass(self.person2));
/// 打印结果:NSKVONotifying_DDPerson 
/// 打印结果:DDPerson

由此我们可以看到 person1 对应的类是NSKVONotifying_DDPerson,而 person2对应的才是DDPerson
KVO实现流程:如果我们对一个实例对象的属性设置kvo时,OC会动态创建一个继承自类对象的类对象,并且让该实例对象的isa指向新创建的类对象。而没有添加KVO的实例变量的isa直接指向自己对象的类对象。如下图
在这里插入图片描述
KVO内部修改值过程
当我们修改成员变量的值的时候,此时会调用新创建的这个类对象的set方法。这个set方法实际调用的是Foundation框架中的_NSSetIntValueAndNotify函数。这个函数会做三件事,首先会调用willChangeValueForKey: 然后调用父类对象中的set方法给属性赋值,接线来调用的是didChangeValueForKey:方法,这个方法的内部内部会调用observer的observeValueForKeyPath:ofObject:change:context:。
⚠️⚠️⚠️ 注意:_NSSetIntValueAndNotify 这个方法不是固定的,如果属性是double类型,那么会调用_NSSetDoubleValueAndNotify。也就是会调用_NSSetXXXValueAndNotify,XXX就是属性类型

总结

当给实例对象的属性添加KVO后

  • OC会动态创建NSKVOAndNotifying_DDPerson类对象
  • 调用NSKVOAndNotifying_DDPerson对象中的set方法
  • set方法自动调用Foundation中的_NSSetXXXValueAndNotify方法
  • _NSSetXXXValueAndNotify内部调用willChangeValueForKey:
  • 调用原来的setter方法
  • 调用didChangeValueForKey:
  • didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

补充——手动触发KVO

想要手动触发KVO,那么就得搞清楚流程,触发KVO其实调用是setter方法。我们只需把setter方法内部实现以下就行了。所以我们只需手动调用willChangeValueForKey:didChangeValueForKey: 两个方法就行了

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.person1 willChangeValueForKey:@"age"];
	[self.person1 didChangeValueForKey:@"age"];
	/// 这样的话 控制台依然会打印
	/*
	 age -- {
    	kind = 1;
    	new = 10;
    	old = 10;
	}
	*/
}

完结撒花,如果有误,欢迎指正。大家加油

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值