KVC是由NSKeyValueCoding非正式协议启用的一种机制,对象采用该协议提供对其属性的间接访问。当对象符合键值编码时,其属性可以通过简洁、统一的消息传递接口通过字符串参数进行寻址。这种间接访问机制补充了实例变量及其相关访问器方法提供的直接访问。键值编码兼容对象提供了一个简单的消息传递接口,该接口在其所有属性中都是一致的。键值编码是许多其他Cocoa技术的基础概念,如键值观察、Cocoa绑定、核心数据和AppleScript功能。在某些情况下,键值编码也有助于简化代码。
1.KVC键值查找流程
1.1.KVC-setValue:(id)value forKey:(NSString *)key的查找流程如下图:
简单来说,会依次查找-(void)setKey:(id)value
,-(void)_setKey:(id)value
,-(void)_setIsKey:(id)value
这3个实例方法,如果存在则执行相应的方法,如果不存在则判断accessInstanceVariablesDirectly
是否等于YES
。如果为真则会依次查找成员_key
,_isKey
,key
,isKey
,如果存在则使用runtime进行赋值,否则或accessInstanceVariablesDirectly
为假,则看是否实现了-(void)setValue:(id)value forUndefinedKey:(NSString *)key
方法,未实现则抛出异常。
1.2.KVC-valueForKey:(NSString *)key的查找流程如下图:
简单来说,会依次查找-(id)getKey
,-(id)key
,-(void)isKey
,-(void)_key
这4个实例方法,如果存在则执行相应的方法,如果不存在则判断accessInstanceVariablesDirectly
是否等于YES
。如果为真则会依次查找成员_key
,_isKey
,key
,isKey
,如果存在则使用runtime进行取值,否则或accessInstanceVariablesDirectly
为假,则看是否实现了-(id)valueForUndefinedKey:(NSString *)key
方法,未实现则抛出异常。
以上的存取规则在
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath
和- (id)valueForKeyPath:(NSString *)keyPath
中同样适用。
2.KVC对结构体类型的存取
定义一个结构体类型 NXBookstruct
,和一个类类型NXBookclass
。
typedef struct {
NSString *name;
NSInteger priceValue;
}NXBookstruct;
@interface NXBookclass : NSObject
@property (nonatomic) NXBookstruct bookstruct;
@end
@implementation NXBookclass
@end
那么采用KVO的方式如何设置和读取结构体类型属性的值呢?
NXBookclass *book = [[NXBookclass alloc] init];
{
NXBookstruct bookstruct = {@"Chinese", 28};
NSValue *value = [NSValue valueWithBytes:&bookstruct objCType:@encode(NXBookstruct)];
[bookclass setValue:value forKey:@"bookstruct"];
NSLog(@"通过类属性直接访问bookstruct:name=%@, priceValue=%@", bookclass.bookstruct.name, @(bookclass.bookstruct.priceValue));
//通过类属性直接访问bookstruct:name=Chinese, priceValue=28
}
{
NXBookstruct bookstruct;
NSValue *value = [bookclass valueForKey:@"bookstruct"];
if (@available(iOS 11.0, *)) {
[value getValue:&bookstruct size:sizeof(NXBookstruct)];
} else {
[value getValue:&bookstruct];
}
NSLog(@"通过KVC间接访问bookstruct:name=%@, priceValue=%@", bookstruct.name, @(bookstruct.priceValue));
//通过KVC间接访问bookstruct:name=Chinese, priceValue=28
}
3.KVC操作符方面的运用
集合操作符方面的运用常见列举如下:
-
@count
:数组中元素的个数 -
@avg.key
:数组中key对应的值的平均值 -
@max.key
:数组中key对应的值的最大值 -
@min.key
:数组中key对应的值的最小值 -
@sum.key
:数组中key对应的值的总和 -
@distinctUnionOfObjects.key
:返回数组中key对应值构成的数组,去除重复 -
@unionOfObjects.key
:返回数组中key对应值构成的数组,不去除重复 -
@distinctUnionOfArrays.key
:返回二维数组中key对应值构成的数组,去除重复 -
@unionOfArrays.key
:返回二维数组中key对应值构成的数组,不去除重复
定义一个class NXBookclass
:
@interface NXBookclass : NSObject
@property (nonatomic) NXBookstruct bookstruct;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger priceValue;
@end
@implementation NXBookclass
@end
执行以下代码,可以进行验证:
NSMutableArray <NXBookclass *>*bookclasses = [NSMutableArray arrayWithCapacity:5];
for (NSInteger i = 1; i <= 9; i++){
NXBookclass *bookclass = [[NXBookclass alloc] init];
bookclass.name = @(i).stringValue;
bookclass.priceValue = 10 * i;
if(bookclass.priceValue > 80){
bookclass.priceValue = 80;
}
if(bookclass.priceValue < 20){
bookclass.priceValue = 20;
}
[bookclasses addObject:bookclass];
}
/*bookclasses:20,20,30,40,50,60,70,80,80*/
NSLog(@"@count=%@", [bookclasses valueForKeyPath:@"@count"]);
//@count=9
NSLog(@"@avg.priceValue=%@", [bookclasses valueForKeyPath:@"@avg.priceValue"]);
//@avg.priceValue=50
NSLog(@"@max.priceValue=%@", [bookclasses valueForKeyPath:@"@max.priceValue"]);
//@max.priceValue=80
NSLog(@"@min.priceValue=%@", [bookclasses valueForKeyPath:@"@min.priceValue"]);
//@min.priceValue=20
NSLog(@"@sum.priceValue=%@", [bookclasses valueForKeyPath:@"@sum.priceValue"]);
//@sum.priceValue=450
NSLog(@"@distinctUnionOfObjects.priceValue=%@", [bookclasses valueForKeyPath:@"@distinctUnionOfObjects.priceValue"]);
//@distinctUnionOfObjects.priceValue=(70,40,80,50,20,60,30)
NSLog(@"@unionOfObjects.priceValue=%@", [bookclasses valueForKeyPath:@"@unionOfObjects.priceValue"]);
//@unionOfObjects.priceValue=(20,20,30,40,50,60,70,80,80)
NSMutableArray *unionOfBookclasses = [NSMutableArray arrayWithCapacity:2];
[unionOfBookclasses addObject:bookclasses];
[unionOfBookclasses addObject:bookclasses];
NSLog(@"@distinctUnionOfArrays.priceValue=%@", [unionOfBookclasses valueForKeyPath:@"@distinctUnionOfArrays.priceValue"]);
//@distinctUnionOfArrays.priceValue=(40,80,30,70,20,60,50)
NSLog(@"@unionOfArrays.priceValue=%@", [unionOfBookclasses valueForKeyPath:@"@unionOfArrays.priceValue"]);
//@unionOfArrays.priceValue=(20,20,30,40,50,60,70,80,80,20,20,30,40,50,60,70,80,80)
4.自定义KVC
我们搞清楚了KVC的超着流程之后,就可以按照KVC的原理来自定义一套KVC。
4.1.准备工作
获取一个类的成员变量列表和方法列表
@interface NXApi : NSObject
/*获取成员变量列表:cls当前类,forward是否向上查询父类*/
+ (NSArray *)varList:(Class)cls forward:(BOOL)forward;
/*获取方法变量列表:cls当前类,forward是否向上查询父类*/
+ (NSArray *)methodList:(Class)cls forward:(BOOL)forward;
@end
@implementation NXApi
+ (NSArray *)varList:(Class)cls forward:(BOOL)forward{
if(!cls || [cls isEqual:[NSObject class]]){
return @[];
}
NSMutableArray *retValue = [NSMutableArray arrayWithCapacity:2];
unsigned int count;
Ivar * rss = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Ivar rs = rss[i];
NSString *name = [NSString stringWithFormat:@"%s", ivar_getName(rs)];
[retValue addObject:name];
}
free(rss);
Class superclass = [cls superclass];
if(superclass && forward){
[retValue addObjectsFromArray:[NXApi varList:superclass forward:forward]];
}
return retValue;
}
+ (NSArray *)methodList:(Class)cls forward:(BOOL)forward{
if(!cls || [cls isEqual:[NSObject class]]){
return @[];
}
NSMutableArray *retValue = [NSMutableArray arrayWithCapacity:2];
unsigned int count;
Method *rss = class_copyMethodList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Method rs = rss[i];
NSString *name = [NSString stringWithFormat:@"%s", sel_getName(method_getName(rs))];
[retValue addObject:name];
}
free(rss);
Class superclass = [cls superclass];
if(superclass && forward){
[retValue addObjectsFromArray:[NXApi methodList:superclass forward:forward]];
}
return retValue;
}
@end
4.2 自定义KVC方法
@interface NSObject(NX)
- (void)nx_setValue:(id)value forKey:(NSString *)key;
- (id)nx_valueForKey:(NSString *)key;
@end
@implementation NSObject(NX)
- (void)nx_setValue:(id)value forKey:(NSString *)key{
if (key == nil || key.length == 0) {
return ;
}
NSString *Key = key.capitalizedString;
//1、 set<Key>: / _set<Key>: /setIs<Key>:
NSMutableArray *__selStringArray = [NSMutableArray arrayWithCapacity:3];
[__selStringArray addObject:[NSString stringWithFormat:@"set%@:", Key]];
[__selStringArray addObject:[NSString stringWithFormat:@"_set%@:", Key]];
[__selStringArray addObject:[NSString stringWithFormat:@"setIs%@:", Key]];
for(NSString *selString in __selStringArray){
SEL sel = NSSelectorFromString(selString);
if(sel && [self respondsToSelector:sel]){
[self performSelector:sel withObject:value];
NSLog(@"set/sel:%@", selString);
return;
}
}
//2、 self.class.accessInstanceVariablesDirectly == YES => _<key>,_is<Key>,<key>, is<Key>
if (self.class.accessInstanceVariablesDirectly) {
//获取实例变量列表
NSArray *varStringArray = [NXApi varList:self.class forward:YES];
NSMutableArray *__varStringArray = [NSMutableArray arrayWithCapacity:4];
[__varStringArray addObject:[NSString stringWithFormat:@"_%@", key]];
[__varStringArray addObject:[NSString stringWithFormat:@"_is%@", Key]];
[__varStringArray addObject:key];
[__varStringArray addObject:[NSString stringWithFormat:@"is%@", Key]];
for (NSString *varString in __varStringArray){
if([varStringArray containsObject:varString]){
Ivar ivar = class_getInstanceVariable(self.class, varString.UTF8String);
object_setIvar(self, ivar, value);
NSLog(@"set/var:%@", varString);
return;
}
}
}
//3、 setValue:forUndefinedKey:
if([[NXApi methodList:self.class forward:YES] containsObject:@"setValue:forUndefinedKey:"]){
[self setValue:value forUndefinedKey:key];
}
//4、Exception
}
- (id)nx_valueForKey:(NSString *)key{
if (key == nil || key.length == 0) {
return [NSNull null];
}
NSString *Key = key.capitalizedString;
//1、 搜索实例的方法 get<Key>, key, isKey, _key
NSMutableArray *__selStringArray = [NSMutableArray arrayWithCapacity:4];
[__selStringArray addObject:[NSString stringWithFormat:@"get%@", Key]];
[__selStringArray addObject:key];
[__selStringArray addObject:[NSString stringWithFormat:@"is%@", Key]];
[__selStringArray addObject:[NSString stringWithFormat:@"_%@", key]];
for(NSString *selString in __selStringArray){
SEL sel = NSSelectorFromString(selString);
if(sel && [self respondsToSelector:sel]){
NSLog(@"get/sel:%@", selString);
return [self performSelector:sel];
}
}
// 2、 self.class.accessInstanceVariablesDirectly == YES => _<key>,_is<Key>,<key>, is<Key>
if (self.class.accessInstanceVariablesDirectly) {
//获取实例变量列表
NSArray *varStringArray = [NXApi varList:self.class forward:YES];
NSMutableArray *__varStringArray = [NSMutableArray arrayWithCapacity:4];
[__varStringArray addObject:[NSString stringWithFormat:@"_%@", key]];
[__varStringArray addObject:[NSString stringWithFormat:@"_is%@", Key]];
[__varStringArray addObject:key];
[__varStringArray addObject:[NSString stringWithFormat:@"is%@", Key]];
for (NSString *varString in __varStringArray){
if([varStringArray containsObject:varString]){
Ivar ivar = class_getInstanceVariable(self.class, varString.UTF8String);
NSLog(@"get/var:%@", varString);
return object_getIvar(self, ivar);
}
}
}
// 3、 valueForUndefinedKey:
// 经过测试只要在子类中实现了valueForUndefinedKey:方法就可以正常调用不会报错
if([[NXApi methodList:self.class forward:YES] containsObject:@"valueForUndefinedKey:"]){
return [self valueForUndefinedKey:key];
}
// 4. Exception
return nil;
}
@end