kvc本质

kvc本质

KVC

KVC: 全称Key-Value Coding,也称为键值编码。
KVC可以通过一个key间接访问某个对象属性。
KVC有两个特性:

  1. 可以访问私有成员变量;
  2. 可以修改私有或者系统的成员属性;

KVC有以下四种方法:

- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;

其中,前两个是设置值方法,后面两个是取值方法。
KeyPath可以使用子类里面的数据。Key不可以。

我们简单看一下KVC的使用:

YZPerson *person1 = [[YZPerson alloc] init];
person1.age = 10;//直接赋值
[person1 setValue:@"jack" forKey:@"name"];//间接赋值
[person1 setValue:@"120" forKeyPath:@"weight"];//间接赋值

NSLog(@"person1.age = %@, person1.name = %@, person1.weight = %d", [person1 valueForKey:@"age"], [person1 valueForKeyPath:@"name"], person1.weight);

运行结果:

2020-02-28 11:51:53.654921+0800 Category[1399:80227] person1.age = 10, person1.name = jack, person1.weight = 120

问:KVC修改属性是否可以触发KVO?

找个例子我们试一下:

@interface ViewController ()
@property (strong, nonatomic) Persion *p1;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Persion *p1 = [[Persion alloc] init];
    self.p1 = p1;
    [self.p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"context"];
    [self.p1 setValue:@"rose" forKey:@"name"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"%@ %@ %@", object, change, context);
}

- (void)dealloc
{
    [self.p1 removeObserver:self forKeyPath:@"name"];
}

@end

运行结果:

2020-02-28 13:48:56.604542+0800 test001[1432:397700] <Persion: 0x283918fa0> {
    kind = 1;
    new = rose;
    old = "<null>";
} context

从结果可以看出,使用KVC修改属性值(name),可以触发KVO的监听。

且,经过验证,[self.p1 setValue:@"rose" forKey:@"name"];这句代码进入了

  • (void)setName:(NSString *)name方法里面。
    也就是KVC的改变属性值,进入了属性的setter方法里面,
    从而在didChangeValueForKey:方法中发送通知,实现KVO的监听。

并且,即使不写- (void)setName:(NSString *)name方法
或者不写@property (strong, nonatomic) NSString *name;
设置KVC的属性值改变,也可以使用KVO监听到属性值的改变,即触发KVO

因此,可以说,KVC是基于KVO的实现,KVC修改属性是可以触发KVO的。

KVC的工作流程

setValue:forKey:大致是这样工作的:

img

主要是:
先找方法
方法找到了,直接执行;
方法没有找到,则

判断是否可以直接访问属性
如果不可以直接访问属性,则Crash
如果可以执行访问属性,则

判断有没有响应的属性值
如果找到了属性,则直接执行;
如果没有找到属性值,则Crash

valueForKey:大致是这样工作的:
img

KVC的Crash相关

问:哪些可能会造成KVC的Crash?

设置

设置值的时候,Key找不到,Key为nil,value为nil,value类型不匹配就会Crash
以下几种方法,都会造成KVC的Crash:

key 不是对象的属性值,造成崩溃[self.person setValue:@"10" forKey:@"age2"];
keyPath 不正确,造成崩溃[self.person setValue:@"10" forKeyPath:@"age.xxx"];
key 为 nil,造成崩溃[self.person setValue:@"10" forKey:nil];
value 为 nil,造成崩溃[self.person setValue:nil forKey:@"age"];
value类型不对[self.person setValue:[[NSObject alloc] init] forKey:@"age"];

在这里插入图片描述

根据KVC设置时的查找过程,我们发现,当setValue:forKey:执行失败会调用 setValue: forUndefinedKey:方法,并引发崩溃。

那么,我们可以通过重写setValue: forUndefinedKey:来避免

1. key 不是对象的属性值
2. keyPath 不正确

造成的Crash

在这里插入图片描述

造成的Crash

我们可以利用 Method Swizzling 方法,在 NSObject 的分类中将 setValue:forKey:ysc_setValue:forKey: 进行方法交换。然后在自定义的方法中,添加对 key 为 nil 这种类型的判断。

4. value 为 nil

为非对象设值,造成崩溃 的情况

在调用 setValue:forKey:方法时,系统如果查找到名为 set:方法的时候,会去检测 value 的参数类型,如果参数类型为 NSNmber 的标量类型或者是 NSValue 的结构类型,但是 value 为 nil 时,会自动调用 setNilValueForKey:方法。
这个方法的默认实现会引发崩溃。

所以为了防止这种情况导致的崩溃,我们可以通过重写 setNilValueForKey:来解决。

- (void)setNilValueForKey:(NSString *)key
{
    NSLog(@"不能将%@设成nil",key);
}

取值

取值的时候,Key找不到
当取值的时候,如果找不到key或者key为nil,也会Crash
key 不是对象的属性值,造成崩溃NSLog(@"%@", [self.person valueForKey:@"age2"]);
keyPath 不正确,造成崩溃NSLog(@"%@", [self.person valueForKeyPath:@"age.xxx"]);
key 为 nil,造成崩溃NSLog(@"%@", [self.person valueForKey:nil]);
value 为 nil,NSLog(@"%@", [nil valueForKey:@"age"]);,该方法编译器不通过
在这里插入图片描述

1和2可以通过重写valueForUndefinedKey:方法
3通过方法交换,重写valueForUndefinedKey:,在方法里面加非空判断
4重写setValue:forKey:方法

iOS 开发:『Crash 防护系统』(三)KVC 防护

iOS开发技巧系列—详解KVC(我告诉你KVC的一切)

posted @ 2021-12-30 15:41  任淏  阅读(336)  评论(0编辑  收藏  举报