OC底层原理17 - KVC

2021-03-18  本文已影响0人  卡布奇诺_95d2

简介

KVC的全称是Key-Value Coding,翻译成中文是键值编码,键值编码是由NSKeyValueCoding非正式协议启用的一种机制,对象采用这种机制来提供对其属性的间接访问。当对象是键值编码兼容的对象时,可以通过简洁,统一的消息传递接口通过字符串参数来访问其属性。这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。

常用API

//直接通过Key来取值
- (nullable 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;  

//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;

//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

//如果Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;

//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;

//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

KVC 设值、取值流程探索

这里主要依据官网描述和代码调试配合完成探索。

自定义实现KVC

自定义 KVC 设值

既然已经知道KVC的设值流程,那接下来就模仿系统接口,自定义一个KVC设值接口hq_setValue:forKey:

  1. 异常判断,当key为空时,则不继续操作。
  2. 由于KVC设值是先查找setter方法,因此,先根据查找的顺序定义setter方法名称。
  3. 通过respondsToSelector函数判断当前方法是否存在,若存在,则执行方法并在执行后退出接口。
  4. setter方法不存在,判断是否响应accessInstanceVariablesDirectly方法,即间接访问实例变量,返回YES,继续下一步设值;返回NO,则抛出异常。
  5. 间接访问变量赋值(只会走一次),顺序是:_key、_isKey、key、isKey。
    1. 定义一个收集实例变量的可变数组,通过class_copyIvarList获取对象中ivar列表,通过ivar_getName,获取ivar名称
    2. 通过class_getInstanceVariable方法,获取相应的ivar
    3. 通过object_setIvar方法,对相应的ivar设置值
  6. 如果找不到相关实例变量,则抛出异常。

hq_setValue:forKey:代码如下:

- (void)hq_setValue:(id)value forKey:(NSString *)key{
    if (key == nil || key.length == 0) {
        return;
    }
    //将key值的首字符变成大写,为后续补充set做准备
    NSString* Key = [key capitalizedString];
    //拼接setKey方法名
    NSString* setKey = [[NSString alloc] initWithFormat:@"set%@:", Key];
    //拼接_setKey方法名
    NSString* _setKey = [[NSString alloc] initWithFormat:@"_set%@:", Key];
    //拼接setIsKey方法名
    NSString* setIsKey = [[NSString alloc] initWithFormat:@"setIs%@:", Key];
    
    //依次执行上面的三个方法
    BOOL result = [self hq_performSelectorWithMethodName:setKey value:value];
    if(result){
        return ;
    }
    result = [self hq_performSelectorWithMethodName:_setKey value:value];
    if(result){
        return ;
    }
    result = [self hq_performSelectorWithMethodName:setIsKey value:value];
    if(result){
        return ;
    }
    
    //若未找到setter方法,则需要判断 accessInstanceVariablesDirectly 这个值
    result = [self.class accessInstanceVariablesDirectly];
    if(!result){
        @throw [NSException exceptionWithName:@"HQUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    //开始查找成员变量
    NSArray* ivalArr = [self getIvarListName];
    
    //_<key> _is<Key> <key> is<Key>
    NSString* _key = [NSString stringWithFormat:@"_%@", key];
    NSString* _isKey = [NSString stringWithFormat:@"_is%@", Key];
    NSString* isKey = [NSString stringWithFormat:@"is%@", Key];
    if([ivalArr containsObject:_key]){
        Ivar ivar = class_getInstanceVariable(self.class, _key.UTF8String);
        object_setIvar(self, ivar, value);
        return ;
    }
    else if([ivalArr containsObject:_isKey]){
        Ivar ivar = class_getInstanceVariable(self.class, _isKey.UTF8String);
        object_setIvar(self, ivar, value);
        return ;
    }
    else if([ivalArr containsObject:key]){
        Ivar ivar = class_getInstanceVariable(self.class, key.UTF8String);
        object_setIvar(self, ivar, value);
        return ;
    }
    else if([ivalArr containsObject:isKey]){
        Ivar ivar = class_getInstanceVariable(self.class, isKey.UTF8String);
        object_setIvar(self, ivar, value);
        return ;
    }
    
    //如果找不到相关实例,则抛出异常
    @throw [NSException exceptionWithName:@"HQUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}

自定义 KVC 取值

  1. 异常判断,当key为空时,则不继续操作。

  2. 查找是否存在getter方法,按照查找顺序定义getter方法的方法名称,查找顺序:get<Key> --> <Key> --> is<Key> --> _<key>

  3. getter方法未找到,此时针对集合类型做了一些特殊处理。查找是否存在countOf<Key>方法
    3.1 若存在countOf<Key>方法
    3.1.1 查找是否存在objectIn<Key>AtIndex:或者<Key>AtIndexes:
    3.1.1.1 若存在,则执行objectIn<Key>AtIndex:<Key>AtIndexes:
    3.1.1.2 若不存在,则进入3.1.2
    3.1.2 查找是否存在enumeratorOf<Key>:并且存在memberOf<Key>:
    3.1.2.1 若存在,则执行enumeratorOf<Key>:memberOf<Key>:
    3.1.2.2 若不存在,则进入步骤4
    3.2 若不存在countOf<Key>方法,则进入步骤4

  4. 检查accessInstanceVariablesDirectly这个布尔值
    若为YES,则查找成员变量,查找流程:_<Key> --> _is<Key> --> <Key> --> is<Key>
    若为NO,则直接进入步骤5

  5. 若是以上都查找失败,则执行 valueForUndefinedKey: 方法,应用可重写该方法

    hq_setValue:forKey:代码如下:

- (id)hq_valueForKey:(NSString *)key{
    if (key == nil  || key.length == 0) {
        return nil;
    }
    //查找getter方法 get<Key> --> <key> --> is<Key> --> _<key>
    NSString* Key = [key capitalizedString];
    NSString* getKey = [NSString stringWithFormat:@"get%@", Key];
    NSString* isKey = [NSString stringWithFormat:@"is%@", Key];
    NSString* _key = [NSString stringWithFormat:@"_%@", key];
    
    id value = [self hq_performSelectorWithMethodName:getKey];
    if(value){
        return value;
    }
    value = [self hq_performSelectorWithMethodName:key];
    if (value) {
        return value;
    }
    value = [self hq_performSelectorWithMethodName:isKey];
    if (value) {
        return value;
    }
    value = [self hq_performSelectorWithMethodName:_key];
    if (value) {
        return value;
    }
    
    //查找countOfKey:方法 + objectIn<Key>AtIndex: 或者 <Key>AtIndexes:
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
    NSString *keyAtIndexes = [NSString stringWithFormat:@"%@AtIndexes:", key];
    NSString *enumeratorOfKey = [NSString stringWithFormat:@"enumeratorOf%@",Key];
    NSString *memberOfKey = [NSString stringWithFormat:@"memberOf%@:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    BOOL result = [self respondsToSelector:NSSelectorFromString(countOfKey)];
    if(result){
        [self performSelector:NSSelectorFromString(countOfKey)];
        if([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]){//countOfKey + objectInKeyAtIndex
            int count = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray* retArr = [[NSMutableArray alloc] init];
            for (int index = 0; index<count; index++) {
                IMP imp = class_getMethodImplementation(self.class, NSSelectorFromString(objectInKeyAtIndex));
                id (*func)(id, SEL, int) = (void*)imp;
                id obj = func(self, NSSelectorFromString(objectInKeyAtIndex),index);
                [retArr addObject:obj];
            }
            return retArr;
        }
        if([self respondsToSelector:NSSelectorFromString(keyAtIndexes)]){//countOfKey + keyAtIndexes
            int count = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray* retArr = [[NSMutableArray alloc] init];
            for (int index = 0; index<count; index++) {
                NSIndexSet* indexes = [[NSIndexSet alloc] initWithIndex:index];
                id obj = [self performSelector:NSSelectorFromString(keyAtIndexes) withObject:indexes];
                [retArr addObjectsFromArray:obj];
            }
            return retArr;
        }
        if ([self respondsToSelector:NSSelectorFromString(enumeratorOfKey)] && [self respondsToSelector:NSSelectorFromString(memberOfKey)]) {
            [self performSelector:NSSelectorFromString(countOfKey)];
            id enumerator = [self hq_performSelectorWithMethodName:enumeratorOfKey];
            if(enumerator){
                return [[NSSet alloc] initWithArray:[enumerator allObjects]];
            }
        }
    }
#pragma clang diagnostic pop
    
    //若未找到setter方法,则需要判断 accessInstanceVariablesDirectly 这个值
    result = [self.class accessInstanceVariablesDirectly];
    if(!result){
        @throw [NSException exceptionWithName:@"HQUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    //开始查找成员变量
    NSArray* ivalArr = [self getIvarListName];
    NSLog(@"ivalArr:%@", ivalArr);
    
    // _<Key> --> _is<Key> --> <Key> --> is<Key>
    NSString* _isKey = [NSString stringWithFormat:@"_is%@", Key];
    if([ivalArr containsObject:_key]){
        Ivar ivar = class_getInstanceVariable(self.class, _key.UTF8String);
        return object_getIvar(self, ivar);
    }
    else if([ivalArr containsObject:_isKey]){
        Ivar ivar = class_getInstanceVariable(self.class, _isKey.UTF8String);
        return object_getIvar(self, ivar);
    }
    else if([ivalArr containsObject:key]){
        Ivar ivar = class_getInstanceVariable(self.class, key.UTF8String);
        return object_getIvar(self, ivar);
    }
    else if ([ivalArr containsObject:isKey]){
        Ivar ivar = class_getInstanceVariable(self.class, isKey.UTF8String);
        return object_getIvar(self, ivar);
    }
    
    @throw [NSException exceptionWithName:@"HQUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    return @"";
}

KVC 运算符

KVC异常处理

KVC应用场景

Demo

自定义KVC见Demo源码

上一篇 下一篇

猜你喜欢

热点阅读