KVC的简介
在iOS
开发中,可以通过setValue:forKey:
对一个对象进行成员变量设值,其原理是什么呢?
在调用setValue:forKey:
方法的位置,右键Jump to Definition
NSObject
、NSArray
、NSMutableDictionary
、NSOrderedSet
、NSSet
都可以使用setValue:forKey:
方法,进入第一项查看
该方法是定义在Foundation
框架的NSKeyValueCoding.h
文件中
从这里可知道,这里是对NSObject
添加了一个分类,使其拥有使用KVC
的能力。另外几个也是这样:
这里只看到了NSKeyValueCoding
头文件,没有看到底层实现。可以去查看苹果官方文档对KVC的介绍。
键值编码KVC(Key-value coding)
是通过给对象添加分类的方式,为其提供一种间接访问其属性(成员变量)的机制。
KVC的几种类型
常规类型
Person *p = [Person new];
[p setValue:@"Jake" forKey:@"name"];
NSLog(@"%@",p.name);
复制代码
集合类型
Person *p = [Person new];
NSArray *array = @[@"1",@"2",@"3"];
p setValue:array forKey:@"array"];
NSLog(@"%@",[p valueForKey:@"array"]);
NSMutableArray *mArray = [p mutableArrayValueForKey:@"array"];
mArray[0] = @"100";
NSLog(@"%@",[p valueForKey:@"array"]);
复制代码
聚合操作符
@avg
:平均值@count
:数量@max
:最大值@min
:最小值@sum
:总和
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"length":@(175 + arc4random_uniform(5)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
//消息从array传递给了string
NSLog(@"数组内容:%@", [personArray valueForKey:@"length"]);
float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
NSLog(@"平均值:%f", avg);
int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
NSLog(@"数量:%d", count);
int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
NSLog(@"总和:%d", sum);
int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
NSLog(@"最大值:%d", max);
int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
NSLog(@"最小值:%d", min);
复制代码
访问非对象属性
typedef struct {
float x, y, z;
} ThreeFloats;
Person *p = [Person new];
ThreeFloats floats = {1.,2.,3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[p setValue:value forKey:@"threeFloats"];
NSValue *threeFloatsValue = [p valueForKey:@"threeFloats"];
NSLog(@"%@",threeFloatsValue);
ThreeFloats th;
[threeFloatsValue getValue:&th];
NSLog(@"%f-%f-%f",th.x,th.y,th.z);
复制代码
keyPath访问
Person *p = [Person new];
Job*job = [Job new];
p.job = job;
job.title = @"开发";
[p setValue:@"编码" forKeyPath:@"job.content"];
NSLog(@"%@-%@",[p valueForKeyPath:@"job.title"],[p valueForKeyPath:@"job.content"]);
复制代码
KVC流程
设值流程
对照苹果官方文档的流程,我们来实际操作验证一下:
- 如果实现了
set<Key>:
或_set<Key>
就能够完成设值,set<Key>:
的优先级要大于_set<Key>
2.如果上面的都没实现,accessInstanceVariablesDirectly
这个方法返回YES
,就会按这个顺序找实例变量:_<key>
, _is<Key>
, <key>
, is<Key>
,进行设值
3.以上方法及变量都没实现时,调用setValue: forUndefinedKey:
,并抛出异常,实现这个方法可以拦截异常,防止崩溃。
取值流程
1.按照get<Key>
, <key>
, is<Key>
, _<key>
的顺序查找,找到就返回值。
2.如果以上方法都没有找到,就会查找countOf<Key>
和 objectIn<Key>AtIndex:
(或者<key>AtIndexes:
),如果实现这两个方法,就会返回一个数组
3.如果还没有找到,就查找countOf<Key>
, enumeratorOf<Key>
, memberOf<Key>:
并返回一个NSSet
对象
4.如果上面的都没实现,accessInstanceVariablesDirectly
这个方法返回YES
,就会按这个顺序找实例变量:_<key>
, _is<Key>
, <key>
, is<Key>
,完成取值
5.如果设值的value
是对象指针,就直接返回,不是的话,如果支持NSNumber
,就直接转成NSNumber
类型返回,不支持NSNumber
,则转换为NSValue
对象返回
6.都没有找到,就会执行valueForUndefinedKey:
,没实现该方法会奔溃抛出异常,实现这个方法可以拦截异常,防止崩溃。
自定义KVC
定义调用方法:
@interface NSObject (KVC)
-(void)mw_setValue:(id)value forKey:(NSString *)key;
-(id)mw_valueForKey:(NSString *)key;
@end
复制代码
实现:
#import "NSObject+KVC.h"
#import <objc/runtime.h>
@implementation NSObject (KVC)
-(void)mw_setValue:(id)value forKey:(NSString *)key
{
if (key == nil || key.length == 0) {
return;
}
//1.判断是否实现set<key>,_set<Key>
NSString *Key = key.capitalizedString;
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self mw_performSelectorWithMethodName:setKey value:value]) {
return;
}else if ([self mw_performSelectorWithMethodName:_setKey value:value]) {
return;
}else if ([self mw_performSelectorWithMethodName:setIsKey value:value]) {
return;
}
//2.判断accessInstanceVariablesDirectly的返回值
//2.1 返回NO 调用stValue: forUndefinedKey:
if (![self.class accessInstanceVariablesDirectly] ) {
[self setValue:value forUndefinedKey:key];
return;
}
//2.2 返回YES 判断是否实现_<key>,_is<Key>,<key>,is<Key>
NSMutableArray *mArray = [self getIvarListName];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
//获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
//对相应的 ivar 设置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
//如果找不到相关实例,调用stValue: forUndefinedKey:
[self setValue:value forUndefinedKey:key];
}
-(id)mw_valueForKey:(NSString *)key
{
if (key == nil || key.length == 0) {
return nil;
}
//1.找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
NSString *Key = key.capitalizedString;
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
//2.判断是否能够直接赋值实例变量accessInstanceVariablesDirectly
//2.1返回NO
if (![self.class accessInstanceVariablesDirectly] ) {
[self valueForUndefinedKey:key];
return @"";
}
//3.找相关实例变量进行赋值:_<key> _is<Key> <key> is<Key>
NSMutableArray *mArray = [self getIvarListName];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
//4.找不到调用valueForUndefinedKey
[self valueForUndefinedKey:key];
return @"";
}
#pragma mark - 相关方法
- (BOOL)mw_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *ivarNameChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
NSLog(@"ivarName == %@",ivarName);
[mArray addObject:ivarName];
}
free(ivars);
return mArray;
}
@end
复制代码