KVC
KVC是什么
- KVC是Key-Value-Coding的缩写,通过查询,俗称键值编码。也可以说在iOS开发的过程KVC提供的机制允许我们通过key值访问对象的属性或成员变量,
- 和KVO一样KVC也是针对NSOBject子类的一种方法,其中在
NSKeyValueCoding
中提供了KVC通用的访问方法,分别是getter方法valueForKey
和setter方法setValue:forKey
,以及其衍生的keyPath方法,这两个方法是各个类通用的。并且由KVC提供默认的实现,我们也可以自己重写对应的方法来改变实现。
KVC的基础方法API
- KVC的定义都是对NSObject的扩展来实现的,KVC较为重要的四个方法如下
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- 四个方法可以分为俩类总结
- 设值
-
- (void)setValue:(nullable id)value forKey:(NSString *)key;
-
-(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- 取值:
-
(nullable id)valueForKey:(NSString *)key;
-
- (nullable id)valueForKeyPath:(NSString *)keyPath;
KVC设值
key方法
- 在使用的时候我们将属性名写在Key方法,Value处写我们的属性赋值
Person* person = [[Person alloc]init];
[person setValue:@101 forKey:@"pAge"];
keyPath方法
- 多级访问-KeyPath路径方法,KVC进行多级访问时,类似于属性调用一样用点语法进行访问即可
- 设置两个继承于
NSObject
的类- Person Dog,在Person里设置一个Dog属性,Dog类有一个age属性,我们通过KVC的keyPath路径方法来设置Dog类的age; - person
#import <Foundation/Foundation.h>
#import "Dog.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject {
}
@property (nonatomic, strong)Dog* dog;
@end
@interface Dog : NSObject
@property (nonatomic, assign)int age;
@end
Person* person = [[Person alloc]init];
[person setValue:@120 forKeyPath:@"dog.age"];
id age2 = [person valueForKeyPath:@"dog.age"];![请添加图片描述](https:
NSLog(@"keyPath获得的年龄是%@", age2);
KVC设值的异常
- 在设值的过程里一旦不小心把值置为nil,我们需要重写
setNilValueForKey:
即可
KVC取值
- 和上面设值一样,取值也是2个方法valueForKey,valueForKeyPath,这个和设置一样,多重设值就是keyPath的点语法,这里写一个demo探究一下取值过程的先后顺序,经过测试,取值过程有2个优先级
- 这里仅用一个Person类来探究取值过程的先后顺序
- 剽窃的图
第二优先级
- Person类里设置一个name的成员变量,而编译器在寻找value的时候存在四个不同的命名,_key, _isKey, key, isKey
#import <Foundation/Foundation.h>
#import "Dog.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject {
NSString* _name;
NSString* _isName;
NSString* name;
NSString* isName;
}
- 在调用
valueForKey
方法的时候,我们重写init方法
#import "Person.h"
#import "Dog.h"
@implementation Person
- (instancetype)init {
if (self = [super init]) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
}
return self;
}
Person* person = [[Person alloc]init];
NSString* name = [person valueForKey:@"name"];
NSLog(@"%@", name);
- 当这四种成员变量都存在的时候编译器先去寻找
_key
变量,没有_key呢?
- 经过测试,在进行编译的时候按照
_key,_isKey,key,iskey
的顺序查找,
+ (BOOL)accessInstanceVariablesDirectly
- 其实编译器能否取查找
_key,_isKey,key,iskey
在这之前取决于一个函数
- +(BOOL)accessInstanceVariablesDirectly,
默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索,如果返回NO系统会抛出异常
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
- 抛出异常,我们需要重写图中的
- (id)valueForUndefinedKey:(NSString *)key
返回nil即可解决异常
- (id)valueForUndefinedKey:(NSString *)key {
return nil;
}
- 测试
第一优先级Setter方法
- 前面提到的都是系统在么有找到Setter方法的前提在+
(BOOL)accessInstanceVariablesDirectly
返回YES去寻找key,但最高的优先级是setter方法,编译器在上面的函数返回YES之前寻找setter方法 - person
- 把init和setter方法全部打开,然后设置打印不同的名字,看出现什么,即可代表了优先级的顺序
#import <Foundation/Foundation.h>
#import "Dog.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject {
NSString* _name;
NSString* _isName;
NSString* name;
NSString* isName;
int pAge;
}
#import "Person.h"
#import "Dog.h"
@implementation Person
- (instancetype)init {
if (self = [super init]) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
}
return self;
}
- (NSString*)name {
return @"Hank";
}
- (NSString*)getName {
return @"getHank";
}
- (NSString*)isName {
return @"isHank";
}
- 通过代码可知在setter方法都存在的时候也是有优先级的
,getKey > key > isKey
- 如果setter方法都不存在那么系统会调用
(BOOL)accessInstanceVariablesDirectly
的返回值按照之前的顺序查找value
第一优先级的有序集合类方法
尝试返回一个Int
- (int)getName {
return 10;
}
- 打断点发现,当我们把返回值返回一个int类的时候系统会把 name被包装成了NSDCFNumeber类型,如此引出了第一优先级之后的两个方法
-(NSInteger)countOfName
-(id)objectInNameAtIndex :(NSInteger)index
- 发现来源- 在3个getter方法里返回一个 int 发现系统包装成了
第二优先级,当3个getter方法不存在的时候,系统调用这个方法,生成一个NSKeyValueArray数组! - 测试 Person.m,把第一优先级的方法注释,对比init和上面的方法
#import "Person.h"
#import "Dog.h"
@implementation Person
- (instancetype)init {
if (self = [super init]) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
}
return self;
}
- (NSInteger)countOfName {
return 12;
}
- (id)objectInNameAtIndex :(NSInteger)index{
return @"objectName";
}
- 可以看出俩个函数,一个是返回长度,一个是返回内容的,当setter方法不存在的时候会先调用这个函数,(了解即)
取值优先级的总结
- 第一优先级,先找相关方法-
,getKey > key > isKey
- 上述方法不存在,找-
-(NSInteger)countOfName
And
-(id)objectInNameAtIndex :(NSInteger)index
-当上述第一优先级都不存在的时候,系统查找(BOOL)accessInstanceVariablesDirectly返回值
,YES则按_key,_isKey,key,iskey
顺序寻找变量取值,NO则会抛出异常,需要重写- (id)valueForUndefinedKey:(NSString *)key
方法
KVC批量动态取值方法(2种)
- KVC还可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value
-NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- 也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给User对象的属性赋值。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
- 新建newPerson
@interface newPerson : NSObject
@property (nonatomic, assign) NSInteger pAge;
@property (nonatomic, copy) NSString* pName;
@property (nonatomic, copy) NSString* pSex;
@end
在这里插入代码片#import "Person.h"
#import "ViewController.h"
#import "newPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSDictionary* pDictionary = @{@"pName":@"Lyt", @"pAge":@"19", @"pSex":@"Girl"};
newPerson* Nperson = [[newPerson alloc] init];
[Nperson setValuesForKeysWithDictionary:pDictionary];
NSLog(@"newPerson.pName: %@", Nperson.pName);
NSLog(@"newPerson.pAge: %ld", Nperson.pAge);
NSLog(@"newPerson.pSex: %@", Nperson.pSex);
NSDictionary* returnDictionary = [Nperson dictionaryWithValuesForKeys:@[@"pName", @"pAge", @"pSex"]];
NSLog(@"returnDictionary%@", returnDictionary);
}
@end
类的属性和字典不匹配
- 对于传入的字典里KVC还可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value的方法,如果字典里出现了类没有的属性,系统会崩溃,我把pSex改成了Sex
NSDictionary* pDictionary = @{@"pName":@"Lyt", @"pAge":@"19", @"Sex":@"Girl"};
newPerson* Nperson = [[newPerson alloc] init];
[Nperson setValuesForKeysWithDictionary:pDictionary];
NSLog(@"newPerson.pName: %@", Nperson.pName);
NSLog(@"newPerson.pAge: %ld", Nperson.pAge);
NSLog(@"newPerson.pSex: %@", Nperson.pSex);
重写-(void)setValue:(id)value forUndefinedKey:(NSString *)key
- 如果类和字典不匹配(字典的传入数量大于类),在不匹配的类的M文件里面重写
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
方法即可
#import "newPerson.h"
@implementation newPerson
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:@"Sex"]) {
self.pSex = (NSString*) value;
}
}
@end
总结
- KVO和KVC还有联系,日后会更新KVO和KVC的联系