Skip to content

Latest commit

 

History

History
253 lines (182 loc) · 10.6 KB

4、KVC.md

File metadata and controls

253 lines (182 loc) · 10.6 KB

KVC

KVC(Key-value coding)键值编码,指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值而不需要调用明确的存取方法。

1、KVC中常见方法

我们随便点击进入setValue:forKey方法,我们可以发现里面的方法基本上都是基于NSObjectNSKeyValueCoding分类写的,所以对于所有继承了NSObject的类型,也就是几乎所有的Objective-C对象都能使用KVC(一些纯Swift类和结构体是不支持KVC的),下面是KVC最为重要的四个方法 KVC1

- (nullable id)valueForKey:(NSString *)key;                          //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

NSKeyValueCoding类别中还有其他的一些方法,这些方法在碰到特殊情况或者有特殊需求还是会用到的

+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值正确性�验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。

- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。

- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。

2、KVC的内部实现机制

KVO的setValue:forKey原理

我们先来一张图片可以直接明了的看清楚实现原理 KVC2

  • 1、按照setKey_setKey的顺序查找成员方法,如果找到方法,传递参数,调用方法
  • 2、如果没有找到,查看accessInstanceVariablesDirectly的返回值(accessInstanceVariablesDirectly的返回值默认是YES),
    • 返回值为YES,按照_Key,_isKey,Key,isKey的顺序查找成员变量,如果找到,直接赋值,如果没有找到,调用setValue:forUndefinedKey:,抛出异常
    • 返回NO,直接调用setValue:forUndefinedKey:,抛出异常

KVO的ValueforKey原理

KVC3

  • 1、按照getKey,key,isKey,_key的顺序查找成员方法,如果找到直接调用取值
  • 2、如果没有找到,查看accessInstanceVariablesDirectly的返回值
  • 返回值为YES,按照_Key,_isKey,Key,isKey的顺序查找成员变量,如果找到,直接取值,如果没有找到,调用setValue:forUndefinedKey:,抛出异常
  • 返回NO,直接调用setValue:forUndefinedKey:,抛出异常

3、KVC的使用

KVC基础使用

假设我们有一个Person类,里面有一个age属性,我们给age赋值和取值

Person *p = [[Person alloc]init];
//赋值
[p setValue:@10 forKey:@"age"];
//取值
[p valueForKey:@"age"]

这也是最简单的使用方法了,也是我们平时项目中最常使用的方法了

KVC中使用keyPath

但是当Person类里面有一个student类,里面有一个height属性,我们怎么赋值height属性呢,

@interface Person : NSObject

@property (nonatomic,assign) int age;

@property (nonatomic,strong) Student *stu;

@end

我们能否这样写呢

Person *p = [[Person alloc]init];
//赋值
[p setValue:@10 forKey:@"stu.height"];
//取值
[p valueForKey:@"stu.height"]

我们运行程序打印结果 KVC4

打印结果是this class is not key value coding-compliant for the key stu.height.,所以这个方法是不可以的,但是iOS为我们提供了另一个方法KeyPath

Person *p = [[Person alloc]init];
p.stu = [[Student alloc]init];
[p setValue:@180 forKeyPath:@"stu.height"];
NSLog(@"valueForKey:%@",[p valueForKeyPath:@"stu.height"]);
NSLog(@"stu.height:%f",p.stu.height);

打印结果 KVC5

keyPath除了对当前对象的属性进行赋值外,还可以对其更“深层”的对象进行赋值。KVC进行多级访问时,直接类似于属性调用一样用点语法进行访问即可。

KVC之集合属性

如果我们想要修改集合类型,我们该怎么办呢,不要着急,系统还是很友好的给我们提供了一些方法

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath

- (NSMutableSet *)mutableSetValueForKey:(NSString *)key
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key 
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath

简单使用

Person *p = [[Person alloc]init];
[[p mutableArrayValueForKey:@"list"] addObject:@"test"];
NSLog(@"mutableArrayValueForKey:%@",[p valueForKeyPath:@"list"]);
NSLog(@"%@",p.list);

关于mutableArrayValueForKey:的适用场景,网上一般说是在KVO中,因为KVO的本质是系统监测到某个属性的内存地址或常量改变时会添加上- (void)willChangeValueForKey:(NSString *)key- (void)didChangeValueForKey:(NSString *)key方法来发送通知,但是如果直接改数组的话,内存地址并没有改变。

- (void)viewDidLoad {
	[super viewDidLoad];
	NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
	_p = [[Person alloc]init];
	[_p addObserver:self forKeyPath:@"list" options:options context:nil];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//    [_p.list addObject:@(arc4random()%255)];
//    NSLog(@"打印内存地址:%x",self.p.list);
//    NSLog(@"打印内容:%@",self.p.list);

	[[self.p mutableArrayValueForKey:@"list"] addObject:@(arc4random()%255)];
	NSLog(@"打印内存地址:%x",self.p.list);
	NSLog(@"打印内容:%@",self.p.list);

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

-(void)dealloc{
	[_p removeObserver:self forKeyPath:@"list"]; //一定要在dealloc里面移除观察
}

我们分别用 [_p.list addObject:@(arc4random()%255)];[[self.p mutableArrayValueForKey:@"list"] addObject:@(arc4random()%255)];两个方法修改list内容,我们打印可知 [_p.list addObject:@(arc4random()%255)];方法并没有改变list的内存地址,而使用[[self.p mutableArrayValueForKey:@"list"] addObject:@(arc4random()%255)];list的内存地址改变了。

KVC之字典属性

KVC里面还有两个关于NSDictionary的方法

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

dictionaryWithValuesForKeys:是指输入一组key,返回这组key对应的属性,再组成一个字典 setValuesForKeysWithDictionary是用来修改dic中对应key的属性

这个属性最常用到的地方就是字典转模型 例如我们有一个Student类,

@interface Student : NSObject
@property (nonatomic,assign) float height;
@property (nonatomic,assign) int age;
@property (nonatomic,strong) NSString *name;
@end

我们正常是怎么赋值呢

Student *stu = [[Student alloc]init];
stu.age = 10;
stu.name = @"jack";
stu.height = 180;

如果里面有100个属性呢,我们就需要写100遍 如果使用setValuesForKeysWithDictionary方法呢

Student *stu = [[Student alloc]init];
//在进行网络请求的时候dic不需要我们手写,是后台返回的
NSDictionary *dic = @{@"name":@"jack",@"height":@180,@"age":@10};
[stu setValuesForKeysWithDictionary:dic];

这样是不是简单了好多。

4、KVC异常处理

当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException的异常,并且应用程序Crash。我们可以重写下面两个方法,根据业务需求合理的处理KVC导致的异常。

- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- (void)setNilValueForKey:(NSString *)key;

其中重写这两个方法,在key值不存在的时候,会走下面方法,而不会异常抛出

- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

重写这个方法,当value值为nil的时候,会走下面方法,而不会异常抛出

- (void)setNilValueForKey:(NSString *)key;

5、KVC的正确性验证

在调用KVC时可以先进行验证,验证通过下面两个方法进行,支持key和keyPath两种方式。验证方法默认实现返回YES,可以通过重写对应的方法修改验证逻辑。

验证方法需要我们手动调用,并不会在进行KVC的过程中自动调用

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

在validateValue方法的内部实现中,如果传入的value或key有问题,可以通过返回NO来表示错误,并设置NSError对象。

因为还需要我们手动调用校验,感觉用处不太大。

参考demo