【iOS】KVC
【iOS】KVC
- 前言
- KVC常用方法
- KVC设值
- KVC取值
- KVC使用keyPath
- 字典与模型转化
- 字典转模型
- 模型转字典
- KVC的nil异常处理
- KVC的应用
- KVC实现高阶的消息传递
- 总结
前言
KVC,键值编码,是OC的一种间接访问对象属性方法或成员变量的机制。它允许通过字符串key名称来访问对应的属性方法或成员变量,而不是直接调用getter/setter方法。
KVO传值的内部就是依赖于KVC实现属性值变化的检测和通知机制的。
KVC常用方法
KVC设值
-(void)setValue:(id)value forKey:(NSString *)key;
这里了解一下KVC设值时的内部查找过程:
- 查找setter方法
程序会优先调用setter方法-(void)setStr:(NSString *)str;
,如果存在,就直接调用。
- 如果没有setter方法,调用
+(BOOL)accessInstanceVariablesDirectly;
方法
KVC机制会检查该方法,如果返回YES(默认),表示允许直接访问成员变量;反之如果手动充血该方法,返回NO,则表示禁止访问,KVC不会再去找_age等变量,而是直接调用-(void)setValue:(id)value forUndefinedKey:(NSString *)key;
,直接出触发异常,表示无法找到指定的key。
- 若返回YES,允许访问实例变量,则按照以下顺序查找成员变量:
- _ key
- _isKey
- key
- isKey
找到第一个匹配的成员变量后,即对该变量直接赋值。对于我的例子,搜索顺序是:_str、 _isStr、 str、 isStr。
无论实例变量是在类接口还是类实现定义,或者使用何种访问修饰符(private、protected、public),KVC都能访问并赋值。
下面使用一个demo来测试体现一下KVC设置时内部的查找顺序:
#import <Foundation/Foundation.h>
#import "KVCTest.h"int main(int argc, const char * argv[]) {@autoreleasepool {KVCTest *test = [[KVCTest alloc] init];[test setValue:@"Tom" forKey:@"name"];NSLog(@"_name:%@", test->_name);NSLog(@"_isName:%@", test->_isName);NSLog(@"name:%@", test->name);NSLog(@"isName:%@", test->isName);NSLog(@"@property:%@", test.name);}return 0;
}
- 如果存在setter方法
@interface KVCTest : NSObject {@publicNSString *_name;NSString *_isName;NSString *name;NSString *isName;
}@property(nonatomic, strong) NSString *name;@end
输出:这里我们会发现不止 test.name 被设值了,_name 也被设值了,这是因为我们碰巧定义了一个和属性自动生成的成员变量同名的变量。
- 没有setter方法,accessInstanceVariablesDirectly方法默认返回YES
@interface KVCTest : NSObject {@publicNSString *_name;NSString *_isName;NSString *name;NSString *isName;
}
输出:按照 _str、 _isStr、 str、 isStr 顺序输出。
- accessInstanceVariablesDirectly方法手动重写返回NO
+(BOOL)accessInstanceVariablesDirectly {return NO;
}-(void)setValue:(id)value forKey:(NSString *)key {NSLog(@"出现异常,无法设置");
}
输出:返回NO后不再查找成员变量,直接调用 setValue:(id)value forKey:(NSString *)key 方法。
这样KVC的设值就很清楚了。
KVC取值
-(id)valueForKey:(NSString *)key;
和KVC设值一样,KVC取值也有既定的顺序:
- 首先按照一下顺序查找gettert方法,找到直接调用取值:
- getKey
- key
- isKey
- _key
如果是BOOL或者int类型的值时,会将其包装成一个NSNumber对象。
- 若以上所有方法都没有找到,则查看
+ (BOOL)accessInstanceVariablesDirectly;
方法的返回值,和设值一样,如果返回YES,则直接调用-(id)valueForUndefinedKey:(NSString *)key
方法且抛出 NSUnknowKeyExpection 异常。 - 若返回YES,和设值一样按照 _str、 _isStr、 str、 isStr 的顺序查找成员变量。
同样用一个demo展示一下KVC取值顺序:
KVCTest *test = [[KVCTest alloc] init];
[test setValue:@"4" forKey:@"age"];
id result = [test valueForKey:@"age"];
NSLog(@"结果:%@", result);
- 按顺序查找getter方法
-(int)getAge {return 1;
}-(int)age {return 2;
}-(int)isAge {return 3;
}
输出结果:
注释掉 getAge :
注释掉 age :
注释掉 isAge :
- accessInstanceVariablesDirectly; 方法返回NO
+(BOOL)accessInstanceVariablesDirectly {return NO;
}-(id)valueForUndefinedKey:(NSString *)key {NSLog(@"出现异常");return nil;
}
输出结果:
KVC使用keyPath
当我们要改变的对象是自定义类或者是其它复杂的数据类型时,我们使用keyPath这个方法来代替使用key一层一层监控的复杂操作。
key 和 keyPath 的区别在于:
- key:只能接受当前类所具有的属性和从父类继承过来的属性。
- keyPath:除了能接受当前类的属性,还能接受当前属性的属性,也就是可以接受关系链,然后进行深层访问。
@interface KVCNext : NSObject@property(nonatomic, strong) KVCTest *test;@end
KVCTest *test = [[KVCTest alloc] init];
KVCNext *test1 = [[KVCNext alloc] init];
[test1 setValue:test forKey:@"test"];
[test1 setValue:@"Timi" forKeyPath:@"test.name"];
NSLog(@"%@", [test1 valueForKeyPath:@"test.name"]);
这样就可以设置它的值了:
字典与模型转化
字典转模型
可以使用KVC的的 setValuesForKeysWithDictionary: 方法来传入一个字典,将字典的key和属性名进行匹配,把value赋值给对象的属性,以实现批量存值。
NSDictionary *dic1 = @{@"name":@"Boss", @"age":@"18", @"sex":@"男"};
Person *p1 = [[Person alloc] init];
[p1 setValuesForKeysWithDictionary:dic1];
NSLog(@"name:%@", p1.name);
NSLog(@"age:%ld", (long)p1.age);
NSLog(@"sex:%@", p1.sex);
但是如果遇到属性与字典key不匹配的情况,可以重·-(void)setValue:(id)value forUndefinedKey:(NSString *)key
方法:
NSDictionary *dic2 = @{@"name":@"Timi", @"age":@"20", @"studentSex":@"女"};
Person *p2 = [[Person alloc] init];
[p2 setValuesForKeysWithDictionary:dic2];
NSLog(@"name:%@", p2.name);
NSLog(@"name:%ld", (long)p2.age);
NSLog(@"name:%@", p2.sex);
这种情况,我们的Person类中没有studentSex这个属性,因此如果不重写该方法,系统将会报错。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {NSLog(@"未定义的key:%@, value:%@", key, value);
}
这样当key不匹配时,调用这个函数,就会得到输出:
模型转字典
NSDictionary *tempDic = [dic1 dictionaryWithValuesForKeys:@[@"name", @"age", @"sex"]];
NSLog(@"tempDic:%@", tempDic);
KVC的nil异常处理
一般情况下,不允许KVC给一个对象赋值为nil,此时需要我们自己手动去处理nil异常的部分,通常我们需要重写- (void)setNilValueForKey:(NSString *)key
方法。
KVCTest *test = [[KVCTest alloc] init];
[test setValue:nil forKey:@"age"];
NSLog(@"%@", [test valueForKey:@"age"]);
这里我们尝试将nil赋值给age,此时就会触发方法:
- (void)setNilValueForKey:(NSString *)key {if ([key isEqualToString:@"age"]) {NSLog(@"不能设置成nil状态%@", key);_age = 11;} else {[super setNilValueForKey:key];}
}
输出结果:
KVC的应用
KVC实现高阶的消息传递
对于容器使用一个KVC,实际上并没有对我们的容器进行一个操作,而是将这个方法传递给容器中的每一个元素,然后再重新返回一个容器。
换句话说,KVC可以让我们对数组中每个元素批量取值或调用属性,而不需要写循环。
NSArray *str = @[@"ios", @"web", @"server"];
//capitalizedString:NSString自带属性,返回一个首字母大写的新字符串
NSArray *strTemp = [str valueForKey:@"capitalizedString"];
for (NSString* arr in strTemp) {NSLog(@"%@", arr);
}
NSArray *strLength = [str valueForKeyPath:@"capitalizedString.length"];
for (NSNumber *length in strLength) {NSLog(@"%ld", (long)length.integerValue);
}
输出结果:
总结
以上就是关于对KVC的学习总结,之后还会继续学习相关内容。