什么是KVO
KVO的本质是key-Value Observing 俗称 健值监听 可以用与监听某个对象属性值的改变 观察者模式的一种实现 采用isa_swizzling实现。
如果一个对象想要知道另一个对象属性值的改变 我们就可以使用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