iOS底层探索KVC

720 阅读4分钟

KVC的简介

iOS开发中,可以通过setValue:forKey:对一个对象进行成员变量设值,其原理是什么呢?

在调用setValue:forKey:方法的位置,右键Jump to Definition

图片.png NSObjectNSArrayNSMutableDictionaryNSOrderedSetNSSet都可以使用setValue:forKey:方法,进入第一项查看

图片.png

该方法是定义在Foundation框架的NSKeyValueCoding.h文件中

图片.png 从这里可知道,这里是对NSObject添加了一个分类,使其拥有使用KVC的能力。另外几个也是这样: 图片.png 图片.png 图片.png 图片.png 这里只看到了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流程

设值流程

图片.png 对照苹果官方文档的流程,我们来实际操作验证一下:

  1. 如果实现了set<Key>:_set<Key>就能够完成设值,set<Key>:的优先级要大于_set<Key> 图片.png 图片.png 2.如果上面的都没实现,accessInstanceVariablesDirectly这个方法返回YES,就会按这个顺序找实例变量:_<key>, _is<Key>, <key>, is<Key>,进行设值 图片.png 图片.png 图片.png 图片.png 图片.png 3.以上方法及变量都没实现时,调用setValue: forUndefinedKey:,并抛出异常,实现这个方法可以拦截异常,防止崩溃。 图片.png 图片.png

取值流程

图片.png 1.按照get<Key>, <key>, is<Key>, _<key>的顺序查找,找到就返回值。 图片.png 图片.png 图片.png 2.如果以上方法都没有找到,就会查找countOf<Key>objectIn<Key>AtIndex:(或者<key>AtIndexes:),如果实现这两个方法,就会返回一个数组 图片.png 图片.png 3.如果还没有找到,就查找countOf<Key>, enumeratorOf<Key>, memberOf<Key>:并返回一个NSSet对象 图片.png 4.如果上面的都没实现,accessInstanceVariablesDirectly这个方法返回YES,就会按这个顺序找实例变量:_<key>, _is<Key>, <key>, is<Key>,完成取值 图片.png 图片.png 图片.png 5.如果设值的value是对象指针,就直接返回,不是的话,如果支持NSNumber,就直接转成NSNumber类型返回,不支持NSNumber,则转换为NSValue对象返回 图片.png 图片.png

图片.png 图片.png 6.都没有找到,就会执行valueForUndefinedKey:,没实现该方法会奔溃抛出异常,实现这个方法可以拦截异常,防止崩溃。 图片.png 图片.png

自定义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

KVC流程图

set.png

get.png