详细文档,可以参考官方文档 KVC官方文档 developer.apple.com/library/arc…
KVC(key-value coding),键值编码,NSObject 的非正式协议NSKeyValueCoding(也就是说所有继承了NSObject 的类对象都可以使用KVC),允许开发者通过key来访问对象属性,或者给对象属性赋值.它不需要明确的存取方法,可以在运行时(而不是在编译期确定),动态地访问或者修改对象属性.
KVC(NSKeyValueCoding非正式协议)里面四个常用方法(其他方法下面也有提到,可以参看NSKeyValueCoding里其他方法):
//根据key取值
- (id)valueForKey:(NSString *)key;
//根据key设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//根据keyPath取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//根据keyPath设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
简单设值(setter)默认执行流程(原理)
官方文档
1.首先查找setter访问器方法,按照setKey, _setKey, setIsKey顺序查找,找到一个就返回,不再继续查找. 如果找到访问器方法,开始检查参数类型。如果参数类型是指针对象类型,调用方法即可。如果value是非对象指针类型(简单数值类型或结构体),但是给定的value是nil,则会调用 setNilValueForKey 方法。setNilValueForKey 方法默认实现会抛出NSInvalidArgumentException异常导致程序崩溃,我们可以重写它。 如果找不到访问器方法,则进行下一步2
2.没找到访问器方法,则会调用类方法 accessInstanceVariablesDirectly(默认返回YES),若返回YES,则按照顺序: _key , _isKey , key, isKey进行查找(找到一个不继续查找).如果找到了这样的实例变量,并且其类型是对象指针类型,先release旧对象,再赋值新对象。如果变量类型是其他类型(基本类型),则先使用NSNumber 或者 NSValue 进行转换,再进行赋值操作
3.若没找到访问器方法,也没有找到实例变量,则会调用 setValue:forUndefinedKey:方法. setValue:forUndefinedKey: 方法默认实现会抛出NSUnknownKeyException异常导致程序崩溃,我们可以重写它
备注:这里官方文档没有提及到setIsKey方法(不排除未来会移除该方法),截止目前,该方法依然会被查找!
KVC设值验证
为了方便验证设值流程,我们不使用@property (不想要系统自动给我们生成getter/setter访问器 以及带下划线_key成员变量,注意不会生成其他成员变量) 新建一个Person类
#import <Foundation/Foundation.h>
#import "Address.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, strong) Address * address;
@end
NS_ASSUME_NONNULL_END
//Person.m实现类
#import "Person.h"
@interface Person ()
{
NSString* _name;
NSString* name;
NSString* _isName;
NSString* isName;
NSInteger age;
NSPoint location;
}
@end
@implementation Person
-(void)setIsName:(NSString*)n{
NSLog(@"%s",__func__);
isName = n;
}
-(void)_setName:(NSString*)n{
NSLog(@"%s",__func__);
_name = n;
}
-(void)setName:(NSString*)n{
NSLog(@"%s",__func__);
name = n;
}
- (void)setValue:(id)value forKey:(NSString *)key{
NSLog(@"%s",__func__);
[super setValue:value forKey:key];
NSLog(@"%s",__func__);
}
+ (BOOL)accessInstanceVariablesDirectly{
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* p = [[Person alloc] init];
//[p setValue:@"JayJay" forKey:@"_name"];方式会直接获取实例变量
[p setValue:@"JayJay" forKey:@"name"];
}
return 0;
}
运行断点调试
可以看到,优先查找到setName方法,并且给成员变量值name(可随意修改哪个成员变量)进行设值 接着将setName访问器方法注释掉运行
接着将_setName访问器方法注释掉运行
接着注释掉setIsName方法,再次运行
接着将成员变量_name注释掉,再次运行
接着将成员变量_isName注释掉,再次运行
接着将成员变量name注释掉,再次运行
接着将 isName注释,再次运行 程序崩溃,在方法 setValue:forUndefinedKey: 抛出 NSUnknownKeyException异常,我们可以重写该方法(空实现程序就不会崩溃).这里如果将所有成员变量不注释,注释掉setKey,_setKey,setIsKey这三个方法,而重写类方法 accessInstanceVariablesDirectly,让其返回NO,也会出现NSUnknownKeyException异常导致崩溃
这里防止崩溃,我们重写setValue: forUndefinedKey 方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"%s",__func__);
}
简单设值流程就是 setKey --> _setKey --> setIsKey --> _key --> _isKey --> key --> isKey
简单取值(getter)默认执行流程(原理)
官方文档:
1.查找接收者访问器(这里是getter)方法,按照 getKey,key ,isKey,_getKey , _key 顺序寻找方法,找到一个就返回,不再继续查找 如果方法返回结果是对象指针类型,则直接返回,如果结果的类型是NSNumber支持的数值类型之一,则完成转换并返回NSNumber. 否则,将完成转换并返回NSValue(Mac OS 10.5中的新增功能:任意类型的结果都将转换为NSValues,而不仅仅是NSPoint,NSRange,NSRect和NSSize)
2 . 如果没有找到简单getter方法,则匹配countOfKey方法和objectInKeyAtIndex:方法 或者 countOfKey方法和keyAtIndexes:方法 若匹配到,则动态创建一个NSArray集合代理对象,该对象响应所有NSArray方法并返回。否则,执行下一步3
备注:动态创建的NSArray代理对象会将NSArray接收到的countOfKey、objectInKeyAtIndex: 、keyAtIndexes:的消息返回给符合KVC规则的调用者.此时NSArray代理对象与上面的方法工作时,系统会将该对象当做NSArray对象,这样可以响应所有NSArray方法
3.如果没有找到NSArray的简单getter方法,或者NSArray的存取方法组,则查找有没有countOfKey、enumeratorOfKey、memberOfKey: 命名的方法(三者缺一不可)。如果找到,则动态创建一个NSSet集合代理对象,该对象响应所有NSSet方法并返回。否则,执行下一步4
备注:动态创建的NSSet代理对象会将NSSet接收到的countOfKey、enumeratorOfKey、memberOfKey: 的消息返回给符合KVC规则的调用者.此时NSSet代理对象与上面的方法工作时,系统会将该对象当做NSSet对象,这样可以响应所有NSSet方法
4.如果没有找到简单getter方法,或集合存取方法组,以及类方法accessInstanceVariablesDirectly返回YES(默认返回YES),则按照_key , _isKey , key, isKey顺序进行查找实例,如果找到对应的实例,则立刻获取实例可用的值并跳转到第5步,否则,跳转到第6步.
5.如果取回的是一个指针对象,则直接返回这个结果。如果取回的是简单值类型,并且可以被NSNumber或者NSValue转换,那么转换后再返回
6.如果都没有找到,则会调用 valueForUndefinedKey:方法,会抛出NSUnknownKeyException异常崩溃,可以重写该方法。
备注:这里官方文档没有提及到_getKey (不排除未来移除可能性)方法,但是Xcode API点击进去可以在最后发现下图所示提示(Apple从Mac OS 10.3开始不建议使用下划线开头的KVC方法),实际截至目前仍然会对此方法进行查找
KVC取值验证
#import <Foundation/Foundation.h>
#import "Address.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, strong) Address * address;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@interface Person ()
{
NSString* _name;
NSString* name;
NSString* _isName;
NSString* isName;
NSInteger age;
NSPoint location;
}
@end
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
_name = @"_JayJay";
_isName = @"_isJayJay";
name = @"JayJay";
isName = @"isJayJay";
}
return self;
}
-(NSInteger)countOfName{
NSLog(@"%s",__func__);
return 3;
}
//具体可以参考NSArray里面 API方法
-(NSString*)objectInNameAtIndex:(NSInteger)index{
NSLog(@"%s",__func__);
return @"objectInNameAtIndex";
}
- (NSArray<NSString*> *)nameAtIndexes:(NSIndexSet *)indexes{
NSLog(@"%s",__func__);
return [@[@"111",@"222",@"333"] objectsAtIndexes:indexes];
}
//具体可以参考NSSet里面 API方法
-(NSEnumerator<NSString*> *)enumeratorOfName{
NSLog(@"%s",__func__);
return [@[@"111",@"222",@"333"] objectEnumerator];
}
- (nullable NSString*)memberOfName:(NSString*)object{
NSLog(@"%s",__func__);
return @"memberOfName";
}
-(NSString*)_name{
NSLog(@"%s",__func__);
return _name;
}
-(NSString*)_getName{
NSLog(@"%s",__func__);
return _isName;
}
-(NSString*)isName{
NSLog(@"%s",__func__);
return isName;
}
-(NSString*)name{
NSLog(@"%s",__func__);
return name;
}
-(NSString*)getName{
NSLog(@"%s",__func__);
return _name;
}
- (id)valueForKey:(NSString *)key{
NSLog(@"%s",__func__);
id temp = [super valueForKey:key];
NSLog(@"%s",__func__);
return temp;
}
+ (BOOL)accessInstanceVariablesDirectly{
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* p = [[Person alloc] init];
//[p valueForKey:@"_name"];方式会直接获取实例变量
id result = [p valueForKey:@"name"];
NSLog(@"result = %@",result);
}
return 0;
}
断点调试,首先查找getName方法
注释掉objectInNameAtIndex:方法,再次运行,会发现匹配countOfName和nameAtIndexes:方法
注释掉objectInNameAtIndex:和 nameAtIndexes:方法,再次运行,会发现匹配countOfName,enumeratorOfName,memberOfName:方法(虽然没看到调用它,系统内部去处理了,注释掉这个方法,则会导致匹配失败)
注释掉所有简单getter方法,所有集合存取方法,则会去调用类方法accessInstanceVariablesDirectly(默认返回YES),若返回YES,则按照_name , _isName , name , isName顺序取查找,若找到,直接获取值返回 (类似上面setter过程,这里就不再赘述了)
如果上述所有简单getter方法,所有集合存取方法以及实例变量都没有找到,则会调用valueForUndefinedKey:方法,抛出NSUnknownKeyException异常崩溃,可以重写该方法
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"%s",__func__);
return NULL;
}
NSKeyValueCoding类别中其他方法:
/*
默认返回YES,表示如果没有找到setter或者getter访问器方法
就会按照_key,_iskey,key,iskey的顺序查找实例变量
如果返回NO,则跳过查找实例变量
*/
+ (BOOL)accessInstanceVariablesDirectly;
/*
对属性值正确性进行验证,它可以用来检查set的值是否正确,
同时为不正确的值做一个替换值或者拒绝设置新值并返回错误原因
*/
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
/*
如果属性是一个NSMutableArray,那么可以用这个方法来返回一个集合NSMutableArray
对集合操作的API,里面还有一系列这样的API
*/
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
/*
如果无法搜索到任何和key有关的getter方法或者实例变量
则会调用这个方法,默认会抛出异常
*/
- (nullable id)valueForUndefinedKey:(NSString *)key;
/*
如果无法搜索到任何和key有关的setter方法或者实例变量
则会调用这个方法,默认会抛出异常
*/
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
/*
如果在setValue过程中,给非对象属性赋值nil,就会调用此方法,默认会抛出异常
*/
- (void)setNilValueForKey:(NSString *)key;
/*
输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model模型转字典
*/
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
/*
输入一个字典,给调用者对象各个成员变量赋值,用于将字典转Model模型
*/
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
还有访问集合属性的KVC方法:
KVC使用KeyPath
想象这样一种情形:一个类的成员变量可能是自定义类型或者更复杂类型,我们可以使用KVC来操作这个属性,然后再次使用KVC对自定义的属性或者复杂类型进行操作,显然比较繁琐.于是苹果KVC给我们提供了一个解决方案就是keyPath,就是按路径递归调用(使用“.”号分割)寻找key 来看具体代码:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Address : NSObject
@property (nonatomic, copy) NSString* street;
@end
NS_ASSUME_NONNULL_END
#import "Address.h"
@implementation Address
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p.address = [[Address alloc] init];
[p setValue:@"中南路街道" forKeyPath:@"address.street"];
NSLog(@"address.street = %@", [p valueForKeyPath:@"address.street"]);
}
return 0;
}
KVC异常处理
kvc中主要会出现两种异常:
- 1.一种是UndefinedKey会抛出NSUnknownKeyException异常
- 2.另一种就是NilKey(给非对象类型变量赋值nil)会抛出NSInvalidArgumentException异常
这里NSUnknownKeyException异常上面已经提到过,下面来看NilKey异常
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* p = [[Person alloc] init];
//这里age实例变量是NSInteger 简单数值类型,赋值nil,KVC默认会抛出异常而崩溃
[p setValue:nil forKey:@"age"];
}
return 0;
}
可以重写Person类的setNilValueForKey方法
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"%s",__func__);
}
KVC值类型和结构体类型处理
我们仔细查看-(id)valueForKey:(NSString *)key 知道,方法总是返回一个对象,但是实际应用中完全可能返回的不是对象。那么怎么去处理? 此外- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法中传入的value也必须是对象类型,那又该怎么去处理?当然说的这些Apple早就给我们处理好了。具体就是:对于设值操作,需要我们自己对非对象数据类型(这里主要是值类型或者结构体类型)进使用NSNumber 或者 NSValue来包装.而对于取值操作,Apple已经使用NSNumber 或者 NSValue给我们封装好了非对象数据类型,返回的就是NSNumber 或者 NSValue对象
NSNumber支持的值类型有哪些,具体可以参看Apple 提供的API:
@interface NSNumber (NSNumberCreation)
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSNumber *)numberWithLongLong:(long long)value;
+ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithInteger:(NSInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSValue支持的值类型(macOS 和 iOS有所不同,这里展示的是macOS,具体可以参看API):
+ (NSValue *)valueWithPoint:(NSPoint)point;
+ (NSValue *)valueWithSize:(NSSize)size;
+ (NSValue *)valueWithRect:(NSRect)rect;
+ (NSValue *)valueWithEdgeInsets:(NSEdgeInsets)insets API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
+ (NSValue *)valueWithRange:(NSRange)range;
来看点代码:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//传入的value,是简单数值类型,需要我们自己使用NSNumber来封装(也可以:[p setValue:@20] forKey:@"age"])
[p setValue:[NSNumber numberWithInt:20] forKey:@"age"];
//传入的value,是结构体类型,需要我们自己使用NSValue来封装
[p setValue:[NSValue valueWithPoint:CGPointMake(100, 100)] forKey:@"location"];
//取值操作,这里返回的并不是NSInteger类型,而是NSNumber类型
id age = [p valueForKey:@"age"];
//取值操作,这里返回的并不是NSPoint类型,而是NSValue类型
id location = [p valueForKey:@"location"];
}
return 0;
}
KVC键值验证(Key-Value Validation)
KVC提供了对给定的key来对对应的value进行验证,以确保value是否可用。默认情况下KVC中不会自动调用,而CoreData在保存托管上下文时会自动验证(可以查看Core Data文档developer.apple.com/library/arc… )。 此外在macOS 中 ,Cocoa Bindings 也会建议您应该自动验证(可以查看 Cocoa Bindings相关文档 developer.apple.com/library/arc… )
Person类重写下面方法:
-(BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError{
NSLog(@"%s",__func__);
NSString* value = *ioValue;
if ([value isEqualToString:@"JayJay"]) {
return NO;
}
return [super validateValue:ioValue forKey:inKey error:outError];
}
备注:该方法会在内部调用(BOOL)validateKey: error: 也就意味着我们可以不需要上面方法,而直接使用它进行单独验证!
来看代码:
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSString*key = @"name";
NSString*value = @"JayJay";
NSError* error;
BOOL valid = [p validateValue:&value forKey:key error:&error];
if (valid) {
NSLog(@"键值匹配");
[p setValue:value forKey:key];
}else{
NSLog(@"键值不匹配");
}
}
return 0;
}
再来看如何单独验证 注释掉
Person类中添加方法:
- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
NSLog(@"%s",__func__);
if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 10)) {
if (outError != NULL) {
*outError = [NSError errorWithDomain:@"PersonErrorDomain"
code:110
userInfo:@{ NSLocalizedDescriptionKey
: @"姓名太短了!!!" }];
}
return NO;
}
return YES;
}
KVC处理集合
集合运算符
- Left key path 表示 要进行运算操作的对象.如果这个对象本身就是集合,那么Left key path可以省略不写
- Collection operator 表示运算符具体操作,该符号指示对集合中元素进行某种操作
- Right key path 表示要操作的属性
集合运算符分三类:
- 1.聚合运算 以某种方式合并集合对象,并且返回 Righ key path指定属性相匹配的集合。 注意:@count是个例外,不需要指定Right key path,并且始终返回NSNumber实例
- 2.数组运算 返回一个NSArray实例(返回数组NSArray中对象类型由操作对象属性决定)
- 3.嵌套运算 嵌套运算符在包含其他集合的集合上工作,并返回NSArray或NSSet实例(取决于运算符),该实例以某种方式组合了嵌套集合的对象
1.聚合运算
聚合运算符(Collection operator)有5种: @avg, @count , @max , @min ,@sum
注意:这里聚合运算符只对NSArray,NSSet操作不包括NSDictionary(NSDictionary 仅仅支持@count操作)
来看点例子:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray* pArr = [NSMutableArray array];
for (int i= 0; i< 5; i++) {
Person* p = [[Person alloc ] init];
p.age = i + 20;
p.name = [NSString stringWithFormat:@"JayJay%d",i];
[pArr addObject:p];
}
//@count特殊
NSNumber* count = [pArr valueForKeyPath:@"@count"];
NSLog(@"count: %d",count.intValue);
NSNumber* sum = [pArr valueForKeyPath:@"@sum.age"];
NSLog(@"sum: %d",sum.intValue);
NSNumber* avg = [pArr valueForKeyPath:@"@avg.age"];
NSLog(@"avg: %d",avg.intValue);
NSNumber* min = [pArr valueForKeyPath:@"@min.age"];
NSLog(@"min: %d",min.intValue);
NSNumber* max = [pArr valueForKeyPath:@"@max.age"];
NSLog(@"max: %d",max.intValue);
}
return 0;
}
2.数组运算
数组运算有如下两种:
@distinctUnionOfObjects 返回一个去重数组 @unionOfObjects 返回一个不去重数组
注意:如果有一个对象为nil,就会造成异常崩溃
例子:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray* pArr = [NSMutableArray array];
for (int i= 1; i <= 5; i++) {
Person* p = [[Person alloc ] init];
p.age = 20 + i%2;
p.name = [NSString stringWithFormat:@"JayJay%d",i];
[pArr addObject:p];
}
NSArray *distinctAgePersons = [pArr valueForKeyPath:@"@distinctUnionOfObjects.age"];
NSArray *persons = [pArr valueForKeyPath:@"@unionOfObjects.age"];
}
return 0;
}
这里操作属性是age,简单数值int类型,返回结果数组里面对应的就是NSNumber类型
3.嵌套运算
有三种操作符: @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
代码:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray* pArr = [NSMutableArray array];
for (int i= 1; i <= 5; i++) {
Person* p = [[Person alloc ] init];
p.age = 20 + i;
p.name = [NSString stringWithFormat:@"JayJay%d",i];
[pArr addObject:p];
}
NSMutableArray* moreArr = [NSMutableArray array];
for (int i= 1; i <= 5; i++) {
Person* p = [[Person alloc ] init];
p.age = 22 + i;
p.name = [NSString stringWithFormat:@"More%d",i];
[moreArr addObject:p];
}
NSArray* arrayOfArrays = @[pArr, moreArr];
NSArray *collectedDistinctPersons = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.age"];
NSArray *collectedPersons = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.age"];
}
return 0;
}
KVC处理字典
KVC很方便来处理字典和模型之间的转换,这里需要注意一点的就是要处理好KVC异常(上面已经提到过),来增强程序健壮性
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
示例代码:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* sex;
@property (nonatomic, strong) NSArray* languages;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSDictionary * dic = @{
@"name":@"JayJay",
@"sex":@"男",
@"age":@20,
@"languages":@[@"C++",@"Objective-c",@"Swift",@"Dart",@"Python"]
};
Person* p = [[Person alloc] init];
[p setValuesForKeysWithDictionary:dic];
NSArray* keys = [dic allKeys];
NSDictionary * dict = [p dictionaryWithValuesForKeys:keys];
NSLog(@"dict = %@" ,dict);
}
return 0;
}
KVC使用场景
- 1、动态设置和取值
- 2、访问或者修改私有变量
- 3、模型和字典互转
- 4、修改系统内部UI属性,比如替换系统自带的导航栏、tabbar, 个性化UITextField中的placeHolderText等
- 5、操作集合
- 6、Core Data
- 7、AppleScript
- 8、Cocoa bindings (苹果MVC)
- 9、KVO
- ...
写在最后: 在使用KVC过程中,由于传入的key或者keyPath都是字符串,手动设置或者修改后很容易出错,从而容易Crash.这里我们可以采用iOS反射机制尽量避免这个问题.具体做法就是:通过@selector()获取到方法的SEL,然后通过NSStringFromSelector()将SEL反射为字符串,这样在@selector()中传入方法名的时候,编译器会有合法性检查,如果方法不存在或未实现,则会报黄色警告,这样就可以减少出错的概率