美文网首页
KVC 底层原理实现

KVC 底层原理实现

作者: 若水water | 来源:发表于2021-01-25 21:49 被阅读0次

    首先放出官方文档的连接:(官网很重要哦)
    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107-SW1

    KVC 是什么?

    kvc 键值编码,是通过字符串“key” 来间接访问对象属性的一种机制。相关的API 在 NSObject 的分类 NSKeyValueCoding中。

    • 通过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;
    
    • 属性,是否可以 开启 间接访问变量的方式
    //默认为YES(只读)
    @property (class, readonly) BOOL accessInstanceVariablesDirectly;
    //通过类方法改变其返回值
    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    
    • 异常处理
    //验证是否键值匹配
    //调用原理:会先找你对象中是否实现了 -(BOOL)validate<Key>:error: 方法
    //如果实现了,就根据方法里面的实现逻辑返回YES 或NO,没有实现,以下方法系统默认返回YES。
    - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
    //与上同理,不过是 对属性的属性的一个验证
    - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
    // 在取值的时候找不到对应的key,防止找不到崩溃
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    //在设置值的时候找不到对应的key
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    //防止设置的是空值而发生崩溃
    - (void)setNilValueForKey:(NSString *)key;
    
    • 为对象批量设置属性值,或批量获取
    //指定一组key,找到对应的属性值,以字典的形式返回
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //对一组key:value值,为对象批量设置
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    
    • 访问可变的集合属性
    //可变数组访问
    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    //NSSet里面的元素是无序的,不能通过索引访问,且元素不能重复,与NSArray中的元素不同(有序、可通过索引访问、可重复);
    //可变集合是NSSet的子类,跟NSSet不一样的地方就在于:NSMutableSet中的元素可以修改(增加、删除、替换)
    - (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
    //NSOrderedSet:跟NSSet中不一样的地方在于:有序,可以通过索引访问,元素不能重复;有序可变集合是NSOrderedSet的子类,与NSOrderedSet不同就在于可以增删替换操作。
    - (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key
    
    ----------------------------------以下对应路由访问----------------------------------------
    
    - (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
    
    - (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
    
    - (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
    
    原理分析
    • setValue:forKey
      先看一下官方文档:
    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.

    2. 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.

    3. 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.

    翻译:
    1、先按顺序查找set<Key>,_set<Key>方法,如果第一个方法实现了,就会直接调用第一个,那么不会调用第二个。第一个没有实现才会调用第二个方法
    2、如果上述方法都没有实现,那么会查看类方法accessInstanceVariablesDirectly,是否返回YES,如果返回YES,那么代表可以间接访问该类的成员变量。_<Key>,_is<Key>,<key>,<isKey>,按顺序查找,规则和上述一样。如果找到,就直接给该属性赋值。如果没有找到就跳到第3步
    3、如果上述变量也没有找到,如果类中实现了异常方法 setValue:forUndefinedKey:,则会被调用,否则会抛出异常。

    代码验证setter
    @interface HLPerson : NSObject
    {
    #pragma mark - group4
        /**
         如果以下步骤方法都不存在,并且‘accessInstanceVariablesDirectly’返回YES
         那么会按顺序寻找_<key>, _is<Key>, <key>, or is<Key>,实例变量,如果存在
         直接返回该变量的值
         */
        @public
        NSString *_name;
        NSString *_isName;
        NSString *name;
        NSString *isName;
    //    NSArray *_mySons;
        
    }
    //@property(nonatomic, copy) NSString *name;
    @property(nonatomic, strong) NSArray *sons;
    
    @end
    
    @interface HLPerson ()
    
    
    @end
    
    @implementation HLPerson
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
        }
        return self;
    }
    
    //yes:表示 以下那些方法没有实现,可以访问 _key,_isKey,key,isKey 成员变量
    //no,表示如果上述方法没有实现,也不能去访问 _key,_isKey,key,isKey 成员变量
    //为no,并且方法都没有实现,那么会走到 valueForUndefinedKey 方法
    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    /**
     如果实现了 set<Key>,_set<Key> 方法,会按顺序优先调用
     如果没有实现,并且 accessInstanceVariablesDirectly 为 Yes
     就会给 _<key>, _is<Key>, <key>, or is<Key>,  变量赋值
     如果为 No,那么会触发 setValue:forUndefinedKey: 方法
     */
    - (void)setName:(NSString *)name {
        NSLog(@"%s",__func__);
    }
    
    - (void)_setName:(NSString *)name {
        NSLog(@"%s",__func__);
    }
    
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        NSLog(@"%s",__func__);
    }
    
    - (void)setNilValueForKey:(NSString *)key {
        NSLog(@"%@的值不能为空",key);
    }
    @end
    

    我们先对 name进行访问:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self arrayTest];
    //    [self arrayNesting];
        
        HLPerson *person = [[HLPerson alloc]init];
        [person setValue:@"name" forKey:@"name"];   
    }
    

    打印结果:
    2021-01-25 15:37:23.886114+0800 KVC--001[11876:880083] -[HLPerson setName:]
    再注释掉- (void)setName:name
    2021-01-25 15:38:46.948742+0800 KVC--001[11901:881343] -[HLPerson _setName:]
    将set 方法都注释掉

    HLPerson *person = [[HLPerson alloc]init];
        [person setValue:@"name" forKey:@"name"];
        NSLog(@"name = %@",person->name);
        NSLog(@"_name = %@",person->_name);
        NSLog(@"_isName = %@",person->_isName);
        NSLog(@"isName = %@",person->isName);
    

    打印结果:
    2021-01-25 15:41:37.897000+0800 KVC--001[11950:883978] name = (null)
    2021-01-25 15:41:37.897108+0800 KVC--001[11950:883978] _name = name
    2021-01-25 15:41:37.897208+0800 KVC--001[11950:883978] _isName = (null)
    2021-01-25 15:41:37.897312+0800 KVC--001[11950:883978] isName = (null)
    将所有变量注释掉
    2021-01-25 15:43:11.087560+0800 KVC--001[11992:885503] -[HLPerson setValue:forUndefinedKey:]
    以上过程可以自己去验证一下。

    KVC 取值过程 valueForKey

    上官方文档:

    1. Search the instance for the first accessor method found with a name like get<Key>, <key>, is<Key>, or _<key>, in that order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step.

    2. If no simple accessor method is found, search the instance for methods whose names match the patterns countOf<Key> and objectIn<Key>AtIndex: (corresponding to the primitive methods defined by the NSArray class) and <key>AtIndexes: (corresponding to the [NSArray](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSArrayClassCluster/Description.html#//apple_ref/occ/cl/NSArray) method objectsAtIndexes:).

      If the first of these and at least one of the other two is found, create a collection proxy object that responds to all NSArray methods and return that. Otherwise, proceed to step 3.

      The proxy object subsequently converts any NSArray messages it receives to some combination of countOf<Key>, objectIn<Key>AtIndex:, and <key>AtIndexes: messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name like get<Key>:range:, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSArray, even if it is not.

    3. If no simple accessor method or group of array access methods is found, look for a triple of methods named countOf<Key>, enumeratorOf<Key>, and memberOf<Key>: (corresponding to the primitive methods defined by the [NSSet](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSSetClassCluster/Description.html#//apple_ref/occ/cl/NSSet) class).

      If all three methods are found, create a collection proxy object that responds to all NSSet methods and return that. Otherwise, proceed to step 4.

      This proxy object subsequently converts any NSSet message it receives into some combination of countOf<Key>, enumeratorOf<Key>, and memberOf<Key>: messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were an NSSet, even if it is not.

    4. If no simple accessor method or group of collection access methods is found, and if the receiver's class method [accessInstanceVariablesDirectly](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/EOF/EOControl/Classes/NSObjectAdditions/Description.html#//apple_ref/occ/clm/NSObject/accessInstanceVariablesDirectly) returns YES, search for an instance variable named _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6.

    5. If the retrieved property value is an object pointer, simply return the result.

      If the value is a scalar type supported by NSNumber, store it in an NSNumber instance and return that.

      If the result is a scalar type not supported by NSNumber, convert to an NSValue object and return that.

    6. If all else fails, invoke [valueForUndefinedKey:](https://developer.apple.com/documentation/objectivec/nsobject/1413457-value). This raises an exception by default, but a subclass of NSObject may provide key-specific behavior.
      翻译:
      1、取值时,第一步会按照 get<Key>, <key>, is<Key>, or _<key>,这个顺序来查找对应的实例方法。如果找到跳5,找不到 下一步
      2、如果第一步中的方法没有找到,kvc会查找countOf<Key>objectIn<Key>AtIndex 或者<key>AtIndexes.(当然第二步其实是针对要访问的是数组集合,因为kvc并不知道你要访问的是什么,所以会根据这个过程先去查找,如果我们要访问集合,在需要的时候可以实现这几个方法)。

      • 如果找到countOf<key>和其他两个方法中的一个,则会创建一个相应所有NSArray方法的集合代理对象,并且返回该对象,即NSKeyValueArray。是NSArray的子类。代理对象将接受到的所有NSArray消息,都转换为countOf<Key>,objectIn<Key> AtIndex:<key>AtIndexes:消息的某种组合(例如,调用[array count],就会触发countOf<Key>,遍历array,就会调用countOf<Key>,objectIn<Key> AtIndex:)用来创建键值编码的对象。如果类内实现了get<Key>:range:之类的可选方法,代理对象也会在适当的时候使用该方法。如果没有找到这三个方法,那么进入下一步。
        3、如果没有找到上面的方法,会同时查找countOf <Key>,enumeratorOf<Key>和memberOf<Key>,这三个方法必须同时存在才行。 如果都找到,会创建一个响应所有nsset方法的集合代理对象,并返回该对象,此代理对象,随后将其收到的所有NSSet消息转换为countOf <Key>,enumeratorOf<Key>和memberOf<Key>消息的某种组合,用于创建他的对象。没有找到,会进入下一步。
        4、如果上述方法都没有找到,检查类方法accessInstanceVariablesDirectly是否返回yes,如果是,那么会查找_<key>,_is<Key>,<key>或is<Key>的实例变量。如果找到,直接返回该变量的值,没有找到就跳到6.
        5、根据搜索到的成员变量值的类型,返回不同的结果
        1>如果是对象指针,则直接返回结果
        2>如果是NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回它
        3>如果是NSNumber不支持的标量类型,则转换为NSValue对象,并返回
        6、若上述方法都查找失败,则会执行valueForUndefinedKey:,默认抛出 NSUndefinedKeyException异常.
    代码实现
    @interface HLPerson ()
    
    
    @end
    
    @implementation HLPerson
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
        }
        return self;
    }
    
    //yes:表示 以下那些方法没有实现,可以访问 _key,_isKey,key,isKey 成员变量
    //no,表示如果上述方法没有实现,也不能去访问 _key,_isKey,key,isKey 成员变量
    //为no,并且方法都没有实现,那么会走到 valueForUndefinedKey 方法
    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    
    
    // valueForKey: 方法调用顺序([person valueForKey:@"name"];)
    #pragma mark - group1
    /**
    1、如果实现了getName,会先调用getName 方法,
    2、如果没有实现getName方法,实现了name 方法,则调用name
    3、如果明确声明了属性,就不会调用isName方法,否则会调用
     */
    - (NSString *)getName {
        NSLog(@"%s",__func__);
        return NSStringFromSelector(_cmd);
    }
    
    - (NSString *)name {
        NSLog(@"%s",__func__);
        return NSStringFromSelector(_cmd);
    }
    
    /// 注意:如果明确声明了name 属性,是不会调用 isName 方法的
    - (NSString *)isName {
        NSLog(@"%s",__func__);
        return NSStringFromSelector(_cmd);
    }
    
    - (NSString *)_name {
        NSLog(@"%s",__func__);
        return NSStringFromSelector(_cmd);
    }
    /**
     如果以上步骤都没有实现,那么会调用以下方法,表示没有找到对应的key
     */
    - (id)valueForUndefinedKey:(NSString *)key {
        NSLog(@"没有找到%@",key);
        return key;
    }
    
    @end
    

    调用:

    HLPerson *person = [[HLPerson alloc]init];
    NSLog(@"valueForKey = %@",[person valueForKey:@"name"]);
    

    打印:
    2021-01-25 17:34:11.507661+0800 KVC--001[12719:939227] -[HLPerson getName]
    2021-01-25 17:34:11.507772+0800 KVC--001[12719:939227] valueForKey = getName
    去掉所有的get方法

        HLPerson *person = [[HLPerson alloc]init];
        person->_name = @"_name";
        person->_isName = @"_isName";
        person->name = @"name";
        person->isName = @"isName";
        NSLog(@"valueForKey = %@",[person valueForKey:@"name"]);
    

    打印结果:
    2021-01-25 17:37:04.635216+0800 KVC--001[12760:941948] valueForKey = _name
    再注释掉所有的成员变量

    @interface HLPerson : NSObject
    {
    #pragma mark - group4
        /**
         如果以下步骤方法都不存在,并且‘accessInstanceVariablesDirectly’返回YES
         那么会按顺序寻找_<key>, _is<Key>, <key>, or is<Key>,实例变量,如果存在
         直接返回该变量的值
         */
        @public
    //    NSString *_name;
    //    NSString *_isName;
    //    NSString *name;
    //    NSString *isName;
    //    NSArray *_mySons;
        
    }
    //@property(nonatomic, copy) NSString *name;
    @property(nonatomic, strong) NSArray *sons;
    
    @end
     HLPerson *person = [[HLPerson alloc]init];
     NSLog(@"valueForKey = %@",[person valueForKey:@"name"]);
    

    打印结果:
    2021-01-25 17:38:26.310139+0800 KVC--001[12790:943271] 没有找到name
    2021-01-25 17:38:26.310269+0800 KVC--001[12790:943271] valueForKey = name

    对于集合属性
    @interface HLPerson : NSObject
    {
    #pragma mark - group4
        /**
         如果以下步骤方法都不存在,并且‘accessInstanceVariablesDirectly’返回YES
         那么会按顺序寻找_<key>, _is<Key>, <key>, or is<Key>,实例变量,如果存在
         直接返回该变量的值
         */
        @public
    //    NSString *_name;
    //    NSString *_isName;
    //    NSString *name;
    //    NSString *isName;
    //    NSArray *_mySons;
        
    }
    //@property(nonatomic, copy) NSString *name;
    @property(nonatomic, strong) NSArray *sons;
    
    @end
    
    
    #import "HLPerson.h"
    
    @interface HLPerson ()
    
    
    @end
    
    @implementation HLPerson
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
        }
        return self;
    }
    
    //yes:表示 以下那些方法没有实现,可以访问 _key,_isKey,key,isKey 成员变量
    //no,表示如果上述方法没有实现,也不能去访问 _key,_isKey,key,isKey 成员变量
    //为no,并且方法都没有实现,那么会走到 valueForUndefinedKey 方法
    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    
    - (NSUInteger)countOfMySons {
        NSLog(@"%s",__func__);
        return _sons.count;
    }
    
    //2, 相较于 3 会优先调用
    - (id)objectInMySonsAtIndex:(NSUInteger)index {
        NSLog(@"mySons = %ld",index);
        return [_sons objectAtIndex:index];
    }
    
    //- validateValue:forKey:error: 验证kvc 是否可用时,会调用下面的方法
    - (BOOL)validateMySons:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{  //在implementation里面加这个方法,它会验证是否设了非法的value
        id obj = *value;
        if (![obj isKindOfClass:[NSArray class]] ) {
            return NO;
        }
        NSLog(@"kvc 可用 value = %@",*value);
        return YES;
    }
    

    现在我们取key = mySons,类中没有定义mySons 属性,如果定义了,就会自动生成 getter 和 setter 方法,也会生成_mySons实例变量,那么就不会调用countOfMySonsobjectInMySonsAtIndex方法。

        HLPerson *person = [[HLPerson alloc]init];
        person.sons = @[@"111",@"222",@"333"];
        NSArray *arr = [person valueForKey:@"mySons"];
        NSLog(@"arr = %@",arr); 
    

    当开始打印这个数组的时候,会调用上面提到的两个方法,也就是取值过程中所说的代理对象将接受到的所有NSArray消息,都转换为countOf<Key>,objectIn<Key> AtIndex:和<key>AtIndexes:消息的某种组合
    打印结果:

    2021-01-25 19:06:26.540839+0800 KVC--001[13238:981977] -[HLPerson countOfMySons]
    2021-01-25 19:06:26.540962+0800 KVC--001[13238:981977] -[HLPerson countOfMySons]
    2021-01-25 19:06:26.541053+0800 KVC--001[13238:981977] mySons = 0
    2021-01-25 19:06:26.541162+0800 KVC--001[13238:981977] mySons = 1
    2021-01-25 19:06:26.541274+0800 KVC--001[13238:981977] mySons = 2
    2021-01-25 19:06:26.541414+0800 KVC--001[13238:981977] arr = (
        111,
        222,
        333
    )
    
    异常机制
    • 防止设置nil 而崩溃
    //异常处理
    - (void)setNilValueForKey:(NSString *)key {
        NSLog(@"%@的值不能为空",key);
    }
    
    • 要设置的key 找不到
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        NSLog(@"%s",__func__);
    }
    
    
    • 读取的key 找不到
    /**
     如果以上步骤都没有实现,那么会调用以下方法,表示没有找到对应的key
     */
    - (id)valueForUndefinedKey:(NSString *)key {
        NSLog(@"没有找到%@",key);
        return key;
    }
    
    • 验证键值是否可用
      1、可以在类里直接实现实例方法:如下1,系统不会自动验证,需要手动验证,验证通过再设置对应的key 和value
      2、类中实现 实例方法2.其实方法1的本质会先查询方法2是否返回YES,如果返回YES就继续设置值,否则不能。
      3、 代码3,是应用
    1、
    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError {
        
    }
    
    2、
    //- validateValue:forKey:error: 验证kvc 是否可用时,会调用下面的方法
    - (BOOL)validateMySons:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{  //在implementation里面加这个方法,它会验证是否设了非法的value
        id obj = *value;
        if (![obj isKindOfClass:[NSArray class]] ) {
            return NO;
        }
        NSLog(@"kvc 可用 value = %@",*value);
        return YES;
    }
    3、
      {
        NSArray *normalArr = @[@"dfd",@"sdf",@"ewe",@"dfs"];
        NSError *error;
    //    id value;
    //    提前验证一下 是否键值匹配
        BOOL result = [person validateValue:&normalArr forKey:@"mySons" error:&error];
        if (result) {
            [person setValue:normalArr forKey:@"mySons"];
            NSArray *arr = [person valueForKey:@"mySons"];
            NSLog(@"arr = %@",arr);
        }
    }
    
    自定义KVC

    创建NSObject的分类,添加 自定义 lg_setValue:forKey:lg_valueForKey
    下面是分别的实现过程,就不一行行分析了,代码中有详细注释

    //模拟系统 setValue:forKey:方法
    - (void)lg_setValue:(nullable id)value forKey:(NSString *)key {
                    
    //     源码不开源,猜测,是不安全的,用于分析过程
        if (key == nil || !key.length) {
            return;
        }
    //    key 首字母大写
        NSString * Key = [key capitalizedString];
    //    寻找方法 如果实现了 按次序调用
        NSString *fun1 = [NSString stringWithFormat:@"set%@",Key];
        NSString *fun2 = [NSString stringWithFormat:@"_set%@",Key];
        if ([self performSelectorWithMethodName:fun1 value:value]) {
            NSLog(@"*********%@**********",fun1);
            return;
        } else if ([self performSelectorWithMethodName:fun2 value:value]) {
            NSLog(@"*********%@**********",fun2);
            return;
        }
    //    如果上述方法没有实现 要看accessInstanceVariablesDirectly 是否返回YES
    //    返回No,抛出异常,为YES就继续寻找 key
        if (![self.class accessInstanceVariablesDirectly]) {
            @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
        }
    //    找到对象的所有实例变量
        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 var = class_getInstanceVariable(self.class, _key.UTF8String);
            //给变量设置值
            object_setIvar(self, var, value);
            return;
        } else if ([mArray containsObject:_isKey]) {
            Ivar var = class_getInstanceVariable([self class], _isKey.UTF8String);
            object_setIvar(self, var, value);
            return;
        } else if ([mArray containsObject:key]) {
            Ivar var = class_getInstanceVariable([self class], key.UTF8String);
            object_setIvar(self, var, value);
            return;
        } else if ([mArray containsObject:isKey]) {
            Ivar var = class_getInstanceVariable([self class], isKey.UTF8String);
            object_setIvar(self, var, value);
            return;
        }
    //    若最终还没有找到 就抛出异常
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    

    setter 中用到的相关方法:

    - (BOOL)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;
    }
    
    //获取所有的成员变量
    - (NSMutableArray *)getIvarListName {
        NSMutableArray *mArray = [NSMutableArray array];
        unsigned int count;
        Ivar *varList = class_copyIvarList(self.class, &count);
        for (int i = 0; i< count; i++) {
            Ivar var = varList[i];
            const char *varName = ivar_getName(var);
            NSString *name = [NSString stringWithUTF8String:varName];
            [mArray addObject:name];
        }
        free(varList);
        return mArray;
    }
    

    lg_valueForKey

    - (nullable id)lg_valueForKey:(NSString *)key{
        
        // 1:刷选key 判断非空
        if (key == nil  || key.length == 0) {
            return nil;
        }
    
        // 2:找到相关方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex
        // key 要大写
        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
        
        // 3:判断是否能够直接赋值实例变量
        if (![self.class accessInstanceVariablesDirectly] ) {
            @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
        }
        
        // 4.找相关实例变量进行赋值
        // 4.1 定义一个收集实例变量的可变数组
        NSMutableArray *mArray = [self getIvarListName];
        // _<key> _is<Key> <key> is<Key>
        // _name -> _isName -> name -> isName
        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);;
        }
        return @"";
    }
    
    集合操作符

    创建一个HLSon类

    @interface HLSon : NSObject
    @property(nonatomic, copy) NSString *name;
    @property(nonatomic, copy) NSString *nick;
    @property(nonatomic, copy) NSString *subject;
    @property(nonatomic, assign) NSInteger age;
    @property(nonatomic, assign) int length;
    @end
    
    @implementation HLSon
    
    - (void)setName:(NSString *)name {
        _name = name;
    //    NSLog(@"_name = %@",name);
    }
    
    - (void)setNilValueForKey:(NSString *)key {
        NSLog(@"%@ 不能为空",key);
    }
    
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        NSLog(@"没有找到%@",key);
    }
    
    - (id)valueForUndefinedKey:(NSString *)key {
        NSLog(@"不存在%@",key);
        return key;
    }
    @end
    

    列举几个常用的操作符:
    @avg 平均数,@count 数量;@sum求和 ;@max最大值;@min最小值

    NSMutableArray *sonArray = [NSMutableArray array];
        for (int i = 0; i< 6; i++) {
            HLSon *son = [[HLSon alloc]init];
            NSDictionary* dict = @{
                                   @"name":@"Tom",
                                   @"age":@(18+i),
                                   @"nick":@"Cat",
                                   @"length":@(175 + 2*arc4random_uniform(6)),
                                   };
            [son setValuesForKeysWithDictionary:dict];
            [sonArray addObject:son];
        }
        NSLog(@"%@",[sonArray valueForKeyPath:@"name"]);
    //    平均身高
        float avg = [[sonArray valueForKeyPath:@"@avg.length"] floatValue];
        NSLog(@"avg = %lf",avg);
        
    //   数量
        int count = [[sonArray valueForKeyPath:@"@count.length"] intValue];
        NSLog(@"count = %d",count);
    //    求和
        int sum = [[sonArray valueForKeyPath:@"@sum.length"] intValue];
        NSLog(@"sum = %d",sum);
        
        count = [[sonArray valueForKeyPath:@"@count"] intValue];
        NSLog(@"count = %d",count);
    //    求最大值
        int max = [[sonArray valueForKeyPath:@"@max.length"] intValue];
        NSLog(@"max = %d",max);
        
        int min = [[sonArray valueForKeyPath:@"@min.length"] intValue];
        NSLog(@"min = %d",min);
    
    2021-01-25 21:29:51.680847+0800 KVC--001[14057:1053602] avg = 180.000000
    2021-01-25 21:29:51.681012+0800 KVC--001[14057:1053602] count = 6
    2021-01-25 21:29:51.681205+0800 KVC--001[14057:1053602] sum = 1080
    2021-01-25 21:29:51.681421+0800 KVC--001[14057:1053602] count = 6
    2021-01-25 21:29:51.681597+0800 KVC--001[14057:1053602] max = 185
    2021-01-25 21:29:51.681725+0800 KVC--001[14057:1053602] min = 175
    

    @unionOfObjects, 返回所有的操作对象指定的属性的集合
    @distinctUnionOfObjects,返回所有的操作对象指定的属性的集合,去重

    NSMutableArray *personArray = [NSMutableArray array];
        for (int i = 0;i<6; i++) {
            HLSon *son = [[HLSon alloc]init];
            NSDictionary *dict = @{
                                  @"name":@"Tom",
                                  @"age":@(18+i),
                                  @"nick":@"Cat",
                                  @"length":@(175 + 2*arc4random_uniform(6)),
                                  };
            [son setValuesForKeysWithDictionary:dict];
            [personArray addObject:son];
        }
        NSLog(@"%@",[personArray valueForKey:@"length"]);
        
    //    返回操作对象指定的属性值集合
        NSArray *arr1 = [personArray valueForKeyPath:@"@unionOfObjects.length"];
        NSLog(@"arr1 = %@",arr1);
    //    返回操作对象指定的属性值集合 去重
        NSArray *arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
        NSLog(@"arr2 = %@",arr2);
    
    2021-01-25 21:32:49.539739+0800 KVC--001[14100:1055907] (
        175,
        185,
        175,
        179,
        177,
        175
    )
    2021-01-25 21:32:49.539970+0800 KVC--001[14100:1055907] arr1 = (
        175,
        185,
        175,
        179,
        177,
        175
    )
    2021-01-25 21:32:49.540155+0800 KVC--001[14100:1055907] arr2 = (
        175,
        185,
        177,
        179
    )
    
    嵌套集合操作

    @distinctUnionOfArrays @unionArrays @distinctUnionOfSets 操作对象是嵌套在内层数组中的 对象
    @distinctUnionOfArrays,对所有数组中的对象的指定属性值的集合 求并集 去重
    @unionArrays ,对所有数组中的对象 的 指定属性值 的集合 求并集
    @distinctUnionOfSets,同理,针对的是 NSSet;NSMutableSet;NSOrderSet,NSMutableOrderSet

    NSMutableArray *sonArray1 = [NSMutableArray array];
        for (int i = 0; i < 6; i ++ ) {
            HLSon *son = [[HLSon alloc]init];
            NSDictionary *dict = @{
                                  @"name":@"Tom",
                                  @"age":@(18+i),
                                  @"nick":@"Cat",
                                  @"length":@(175 + 2*arc4random_uniform(6)),
                                  };
            [son setValuesForKeysWithDictionary:dict];
            [sonArray1 addObject:son];
        }
        
        NSMutableArray *sonArray2 = [NSMutableArray array];
        for (int i = 0; i < 6; i++) {
            HLSon *son = [[HLSon alloc]init];
            NSDictionary *dict = @{
                                  @"name":@"Tom",
                                  @"age":@(18+i),
                                  @"nick":@"Cat",
                                  @"length":@(175 + 2*arc4random_uniform(6)),
                                  };
            [son setValuesForKeysWithDictionary:dict];
            [sonArray2 addObject:son];
        }
        
        NSArray *arr1 = [sonArray1 valueForKeyPath:@"@unionOfObjects.length"];
        NSLog(@"arr1 = %@",arr1);
        
        NSArray *arr2 = [sonArray2 valueForKeyPath:@"@unionOfObjects.length"];
        NSLog(@"arr2 = %@",arr2);
        
        NSArray *nestArray = @[sonArray1,sonArray2];
    //    求操作对象属性值的集合 的并集 去重
        NSArray *arr = [nestArray valueForKeyPath:@"@distinctUnionOfArrays.length"];
        NSLog(@"arr = %@",arr);
        
    //    求操作对象属性值的集合的并集
        NSArray *arr3 = [nestArray valueForKeyPath:@"@unionOfArrays.length"];
        NSLog(@"arr3 = %@",arr3);
        
    
    2021-01-25 21:42:05.396213+0800 KVC--001[14184:1060832] arr1 = (
        185,
        185,
        177,
        179,
        185,
        175
    )
    2021-01-25 21:42:05.396487+0800 KVC--001[14184:1060832] arr2 = (
        175,
        181,
        181,
        175,
        185,
        185
    )
    2021-01-25 21:42:05.396756+0800 KVC--001[14184:1060832] arr = (
        185,
        181,
        179,
        177,
        175
    )
    2021-01-25 21:42:05.396982+0800 KVC--001[14184:1060832] arr3 = (
        185,
        185,
        177,
        179,
        185,
        175,
        175,
        181,
        181,
        175,
        185,
        185
    )
    
    KVC的好处
    • 使用更简洁的代码访问对象的属性,仅仅用一个 字符串key 就可以
    • 可以访问和修改 对象的 私有属性和变量
    • 在多值操作中,model 和 字典可以相互转换,效率更快
    • 对集合使用kvc,可以对集合中的每个对象的属性进行操作

    相关文章

      网友评论

          本文标题:KVC 底层原理实现

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