什么是KVO
KVO的本质是key-Value Observing 俗称 健值监听 可以用与监听某个对象属性值的改变。
如果一个对象想要知道另一个对象属性值的改变 我们就可以使用KVO来实现 具体代码如下
#import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic,strong) Person *p; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; p.age = 10; self.p = p; //添加KVO监听 [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.p.age = 20; } //当监听对象的属性值发生改变时 会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"age"]) { NSLog(@"属性值改变了 %@",change); } } -(void)dealloc { [self.p removeObserver:self forKeyPath:@"age"]; } @end
其实被监听的对象在调用setAge的时候
1.系统创建继承与被监听对象的子类 并把监听对象的isa指针指向该类类对象
2.调用该类的setter方法 在这个方法中调用_NSSet*ValueNotify()方法 该方法调用willChangeValueForKey: super的setter方法 和 didChangeValueForKey方法 *代表类型 比如Int
3.在didChangeValueForKey 调用监听者的observeValueForKeyPath: ofObject: change: context:方法完成监听
模拟动态创建的类的代码(伪代码)
#import "NSKVONotifying_LFPerson.h" @implementation NSKVONotifying_LFPerson - (void)setAge:(int)age { _NSSetIntValueNotify(); } void _NSSetIntValueNotify() { [self willChangeValueForKey:@"age"]; [super setAge:age]; [self didChangeValueForKey:@"age"]; } - (void)didChangeValueForKey:(NSString *)key { //通知监听器属性值发生了改变 //通知监听器的observeValueForKeyPath: ofObject: change: context:方法 }
那么iOS中用什么方式实现对一个对象的KVO?(KVO的本质是什么)
首先利用RunTime动态生成一个子类 并且让实例对象的isa指针指向该类
当修改实例对象的属性时 会调用Foundation的_NSSSetXXXValueAndNotify函数
然后调用WillChangeValueForKey 父类的setter方法
最后调用didChangeValueForKey 在其内部会调用监听者的observeValueForKeyPath: ofObject: change: context:方法
如何手动触发KVO呢?
手动调用 willChangeValueForKey 和 didChangeValueForKey方法 单纯调用didChangeValueForKey是不能触发KVO的 因为是要这个方法的内部会去判断是否已经调用了willChangeValueForKey
如果没调用 是不会触发监听者的监听方法的.
KVC的简单使用
KVC的全称Key-Value Coding 俗称键值编码 可以通过一个key来访问某个属性
常见的API有
setValue:forKeyPath:
setValue:forKey
valueForKeyPath:
valueForKey:
前面两个设置属性值 后面两个是获取属性值的
#import <Foundation/Foundation.h> @interface Cat : NSObject @property (nonatomic,assign) int weight; @end NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject @property (nonatomic,assign) int age; @property (nonatomic,strong) Cat *cat; @end NS_ASSUME_NONNULL_END
#import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic,strong) Person *p; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; //KVC赋值 p.cat = [[Cat alloc] init]; [p setValue:@10 forKey:@"age"]; [p setValue:@11 forKeyPath:@"cat.weight"]; NSNumber *age = [p valueForKey:@"age"]; NSNumber *weight = [p valueForKeyPath:@"cat.weight"]; self.p = p; [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.p setValue:@20 forKey:@"age"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"%@",change); } - (void)dealloc { [self.p removeObserver:self forKeyPath:@"age"]; }
通过KVC修改属性会触发KVO吗?
通过上面的代码 我们可以看到是可以触发KVO的. 为什么会这样 其实我们可以追究一下setValue:forKey:这个方法的原理:
1.首先会找相关属性的setKey: 或者_setKey方法 也就是setter方法 即使这个属性没被声明 只要实现了setKey:或者_setKey:也会调用
2.如果这两个方法没被实现 就会访问+(BOOL)accessInstanceVariablesDirectly 方法 返回YES 能够直接访问成员变量 返回NO不能直接访问成员变量 默认的返回值是YES
3.如果返回NO 会闪退 setValue:forUndefinedKey 报这个错 如果返回YES 就是允许他访问成员变量 那么他会按照 _key, _isKey, key, isKey,顺序查找成员变量
4.如果找到了 直接赋值 如果找不到会闪退 setValue:forUndefinedKey
如果我们key是成员变量而不是属性 也会触发KVO 其实KVC内部赋值后应该会自行通知KVO的观察者的 内部实现了willChageForKey:和didChangeForKey
所以如果我们的KVC是赋值成员变量的话 内部其实是这样的
[p willChangeValueForKey:@"age"];
p->_age = 10;
[p didChangeValueForKey:@"age"];
KVC的赋值和取值过程是怎样的? 原理是什么?
取值的过程
1.首先会找相关属性的setKey: 或者_setKey方法 也就是setter方法 即使这个属性没被声明 只要实现了setKey:或者_setKey:也会调用
2.如果这两个方法没被实现 就会访问+(BOOL)accessInstanceVariablesDirectly 方法 返回YES 能够直接访问成员变量 返回NO不能直接访问成员变量 默认的返回值是YES
3.如果返回NO 会闪退 setValue:forUndefinedKey 报这个错 如果返回YES 就是允许他访问成员变量 那么他会按照 _key, _isKey, key, isKey,顺序查找成员变量
4.如果找到了 直接赋值 如果找不到会闪退 setValue:forUndefinedKey
获取值的过程
1.按照 getKey, key, isKey _key的方法去查找 如果有 就调用这个方法 返回值
2.没找到方法 会访问+(BOOL)accessInstanceVariablesDirectly 返回NO 调用valueForUndeFinedKey:抛出异常 NSUnKnownKeyException
3.如果返回YES则按顺序查找_key _isKey key isKey成员变量 如果找到返回该值 找不到 调用valueForUndeFinedKey:抛出异常 NSUnKnownKeyException