美文网首页
19.iOS底层学习之iOS底层学习之KVC

19.iOS底层学习之iOS底层学习之KVC

作者: 牛牛大王奥利给 | 来源:发表于2021-11-08 13:19 被阅读0次

    本篇提纲
    1、KVC的基本介绍
    2、KVC的API
    3、KVC的写入过程
    4、KVC的读取过程
    5、自定义KVC

    KVC的基本介绍

    本篇KVC的内容主要依赖于官方的文档,并且通过实际操作来验证这个过程。
    KVC是Key-Value Coding的简称,键值编码是通过NSKeyValueCoding的非正式协议来提供间接的访问对象属性的一种途径。当一个对象符合键值编码时,它的属性可以通过简洁、统一的消息传递接口通过字符串参数进行寻址。这种间接的访问机制补充了实例变量以及相关的访问器方法提供的直接访问。

    我们一般习惯于使用存取方法来获取对象的属性。一个get方法返回属性值,set方法设置属性值。在oc中,你也可以直接通过实例变量访问属性。这些方法都可以直接的访问一个对象的属性,但是需要通过调用对应的属性方法或者变量名。随着属性列表的增加和更改,那么访问这些属性的代码也必须增加或者更改。相比之下,键值编码对象提供了简单的可以获取所有属性的消息接口。

    以上主要翻译于文档对KVC的介绍。参考资料

    KVC的API

    • 通过Key读取和写入
    - (nullable id)valueForKey:(NSString *)key;
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    
    • 通过KeyPath读取和写入
    - (nullable id)valueForKeyPath:(NSString *)keyPath;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    
    • 读取时完全找不到Key值之后调用的方法
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    
    • 赋值时的完全找不到Key值之后调用的方法
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    
    • 其他API
    //value为nil调用的方法
    - (void)setNilValueForKey:(NSString *)key;
    //输入一组Key,返回该组Key对应的Value,再转成字典返回
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //设置一组键值
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    
    实际验证KVC
    2.1使用Key来寻址
    使用Key来寻址.png
    2.2使用KeyPath来寻址
    无嵌套属性.png
    带嵌套属性.png
    2.3数组元素
    数组相关.png
    2.4模型字典转换
    转换.png

    KVC的写入过程

    先来看文档上说的写入过程:

    1. Look for the first accessor named set<Key>: or _set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.
    1. If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.
    1. Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:. This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.
    • 先去查找set<Key>,然后是_set<Key>方法,按照这个顺序如果查找到了调用方法并且结束
    • 如果没找到上面的两个方法,并且类方法accessInstanceVariablesDirectly返回是YES,那么按照实例变量名称为_<key>,_is<key>,<key>,is<key>的顺序去查找,如果找到了直接赋值并且结束。
    • 如果上面都没找到,那么会调用setValue:forUndefinedKey:方法,这个方法默认会抛出一个异常,但是继承自NSObject的子类也有可能对这个方法重写并且完成具体明确的键值操作处理。
    KVC的写入过程代码验证

    我根据上面的文档先实现了全部的方法:


    image.png
    • 去掉set<key>运行结果


      image.png
    • 去掉_set<key>,并且没有实现accessInstanceVariablesDirectly方法的运行结果


      image.png

    直接报❌了!!!!!!!!!!

    因为实现的get方法里有几个相关is的方法,我们set里也加上,看看走不走。

    • setIs<key>运行结果和_setIs<key>的运行结果
    image.png
    image.png

    经过上面的验证发现方法setIs<key>走了,然后_setIs<key>是没有执行的。

    • 实现accessInstanceVariablesDirectly方法之后
      image.png

    去掉实例变量_KVCTest


    image.png

    去掉_isKVCTest


    image.png

    去掉KVCTest


    image.png

    验证完毕,总结一下

    • 在setValueForKey的过程中,先会找set<key>,然后是_set<key>,然后是setIsKey如果找到了方法,调用并结束。
    • 如果没找到,并且没有实现方法accessInstanceVariablesDirectly,那么直接回抛出异常setValue:forUndefinedKey:
    • 如果没找到,但是实现了accessInstanceVariablesDirectly并且返回YES,那么开始按照_key,_iskey,key,iskey的顺序进行直接set赋值,并结束;
    • 如果以上还都没有找到,那么直接回抛出异常setValue:forUndefinedKey:

    KVC的读取过程

    相比于存储过程,kvc的读取过程就要更加复杂一些。
    1、查找getter方法,查找顺序是:get<Key>,<key>,is<Key>,_<key>
    如果找到,进入到第五步;
    如果没找到,进入第二步。

    2、查找countOf<Key>objectIn<Key>AtIndex:<key>AtIndexes:

    如果找到了其中的任意一个方法,那么会创建一个集合代理对象,该对象响应所有的NSArray方法并返回该对象。

    代理对象随后将其接收到的任何NSArray消息转换为countOf<Key>、objectIn<Key>AtIndex:、和<Key>AtIndex:消息的组合,并转换为创建它的键值编码兼容对象。如果原始对象还实现了一个名为get<Key>:range:的可选方法,那么代理对象也会在适当的时候使用该方法。代理对象与键值编码兼容对象一起工作,基本属性如同NSArray一样。

    如果没找到执行步骤3。

    3、去查找countOf<Key>Enumeratorf<Key>memberOf<Key>:

    如果找到了其中的任意一个方法,那么会创建一个集合代理对象,该对象响应所有的NSSet方法并返回该对象。

    代理对象随后将其接收到的任何NSSet消息转换为countOf<Key>、Enumeratorf<Key>和memberOf<Key>:消息的组合,并转换为创建它的键值编码兼容对象。代理对象与键值编码兼容对象一起工作,基本属性如同NSSet一样。

    如果没找到执行步骤4。

    4、查找accessInstanceVariablesDirectly方法的返回值
    如果返回YES,那么按照 _<key>, _is<Key>, <key>, is<Key>的顺序进行查找实例变量,如果找到了进入到步骤5,没找到进入步骤6

    5、如果是对象指针,则直接返回结果;
    如果是NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回它;
    如果是NSNumber不支持的标量类型,转换为NSValue对象并返回该对象;

    6、如果以上所有方法都失败,系统调用该对象的valueForUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常。

    自定义KVC

    • 定义获取实例变量列表方法
    //获取实例变量的方法 返回变量数组
    - (NSMutableArray *)getIvarLsitName{
        NSMutableArray * mArray = [NSMutableArray arrayWithCapacity:1];
        int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivars[I];
            char * ivarNameChar = ivar_getName(ivar);
            NSString * ivarName = [NSString stringWithUTF8String:ivarNameChar];
            [mArray addObject:ivarName];
        }
        free(ivars);
        return mArray;
    }
    
    • 定义是否可实现选择器方法
    //自定义检测是否有方法实现方法
    - (BOOL)nn_performSelectorWithMethodName:(NSString *)methodName Value:(id)value{
        if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
            [self performSelector:NSSelectorFromString(methodName)];
            return YES;
        }
        return NO;
    }
    
    - (id)performSelectorWithMethodName:(NSString *)methodName{
        if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
            return [self performSelector:NSSelectorFromString(methodName)];
        }
        return nil;
    }
    
    • 自定义setValueForKey
    //自定义kvc赋值过程 set
    - (void)nn_setValue:(id)value forKey:(NSString *)key{
        //1、判空
        if (key.length == 0||key == nil) {
            return;
        }
        //2、settet:set<Key>: -> _set<Key> - >setIs<Key>
        //Key要大写
        NSString *Key = key.capitalizedString;
        //拼接方法set<Key>、_set<Key>、setIs<Key>
        NSString * setKey = [NSString stringWithFormat:@"set%@:",Key];
        NSString * _setKey = [NSString stringWithFormat:@"_set%@:",Key];
        NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    
        //根据kvc的Set顺序来进行判断
        if ([self nn_performSelectorWithMethodName:setKey Value:value]) {
            NSLog(@"nn=============%@",setKey);
            return;
        }else if ([self nn_performSelectorWithMethodName:_setKey Value:value]){
            NSLog(@"nn============%@",_setKey);
            return;
        }else if ([self nn_performSelectorWithMethodName:setIsKey Value:value]){
            NSLog(@"nn============%@",setIsKey);
            return;
        }
        
        //3、判断accessInstanceVariablesDirectly 是否返回YES,开启查找实例变量的流程
        
        //未开启 直接抛出异常
        if (![self.class accessInstanceVariablesDirectly]) {
            @throw [NSException exceptionWithName:@"NNUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
        }
        
        //4、开启了 开始查找实例变量 按照_key _isKey key isKey的顺序去查找
        NSMutableArray * mArray = [self getIvarLsitName];
        NSString * _key = [NSString stringWithFormat:@"_%@",key];
        NSString * _isKey = [NSString stringWithFormat:@"_is%@",Key];
        NSString * isKey = [NSString stringWithFormat:@"is%@",Key];
        
        if ([mArray containsObject:_key]) {
            //首先查_key 查到赋值
            Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
            NSLog(@"nn=============object_setIvar%@",_key);
            object_setIvar(self, ivar, value);
            return;
        }else if ([mArray containsObject:_isKey]){
            //查_isKey
            Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
            NSLog(@"nn=============object_setIvar%@",_isKey);
            object_setIvar(self, ivar, value);
            return;
        }else if([mArray containsObject:key]){
            //查key
            Ivar ivar = class_getInstanceVariable([self class], Key.UTF8String);
            NSLog(@"nn=============object_setIvar%@",key);
            object_setIvar(self, ivar, value);
            return;
        }else if ([mArray containsObject:isKey]){
            //查isKey
            Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
            NSLog(@"nn=============object_setIvar%@",isKey);
            object_setIvar(self, ivar, value);
            return;
        }
        
        //如果都没查到 抛出异常
        @throw [NSException exceptionWithName:@"NNUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    
    }
    
    • 自定义valueForKey
    //自定义kvc读取的过程
    - (id)nn_valueForKey:(NSString *)key{
        //1、判空
        if (key.length == 0||key == nil) {
            return nil;
        }
        
        //2、按照查找顺序_key,_isKey,get<Key>,isKey,countOfKey
        NSString *Key = key.capitalizedString;
        NSString * _key = [NSString stringWithFormat:@"_%@",key];
        NSString * _isKey = [NSString stringWithFormat:@"_is%@",Key];
        NSString * isKey = [NSString stringWithFormat:@"is%@",Key];
        NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    
        if ([self respondsToSelector:NSSelectorFromString(_key)]) {
            //_key
            NSLog(@"nn=============nn_valueForKey%@",_key);
            return [self performSelector:NSSelectorFromString(_key)];
            
        }else if ([self respondsToSelector:NSSelectorFromString(_isKey)]){
            //_isKey
            NSLog(@"nn=============nn_valueForKey%@",_isKey);
            return [self performSelector:NSSelectorFromString(_isKey)];
            
        }else if ([self respondsToSelector:NSStringFromSelector(key)]){
            //key
            NSLog(@"nn=============nn_valueForKey%@",key);
            return [self performSelector:NSSelectorFromString(key)];
            
        }else if ([self respondsToSelector:NSSelectorFromString(isKey)]){
            //isKey
            NSLog(@"nn=============nn_valueForKey%@",isKey);
            return [self performSelector:NSSelectorFromString(isKey)];
        }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
            //countOfKey
            NSString * objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
            if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
                int num = [self performSelector:NSSelectorFromString(countOfKey)];
                NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
                for (int i = 0; i < num-1; i++) {
                    id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex)];
                }
                
                for (int j = 0; j < num; j++) {
                    id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex)];
                    [mArray addObject:objc];
                }
                NSLog(@"nn=============nn_valueForKey array==%@",mArray);
                return mArray;
            }
        }
        
        //3、判断accessInstanceVariablesDirectly 是否返回YES,开启查找实例变量的流程
        
        //未开启 直接抛出异常
        if (![self.class accessInstanceVariablesDirectly]) {
            @throw [NSException exceptionWithName:@"NNUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
        }
        
        // 4、 找相关实例变量进行赋值
           NSMutableArray *mArray = [self getIvarListName];
           // _<key>→_is<Key>→<key>→is<Key>
           // _name→_isName→name→isName
           if ([mArray containsObject:_key]) {
               Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
               NSLog(@"nn=============class_getInstanceVariable %@",_key);
               return object_getIvar(self, ivar);
           }else if ([mArray containsObject:_isKey]) {
               Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
               NSLog(@"nn=============class_getInstanceVariable %@",_isKey);
               return object_getIvar(self, ivar);
           }else if ([mArray containsObject:key]) {
               Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
               NSLog(@"nn=============class_getInstanceVariable %@",key);
               return object_getIvar(self, ivar);
           }else if ([mArray containsObject:isKey]) {
               Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
               NSLog(@"nn=============class_getInstanceVariable %@",isKey);
               return object_getIvar(self, ivar);
           }
        
        return @"";
    }
    
    • 实现效果截图


      image.png

    相关文章

      网友评论

          本文标题:19.iOS底层学习之iOS底层学习之KVC

          本文链接:https://www.haomeiwen.com/subject/zdlliltx.html