iOS KVO KVC 详解

什么是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

 

posted @ 2020-11-05 23:50  幻影-2000  阅读(162)  评论(0编辑  收藏  举报