iOS之KVC

作者: 默默_David | 来源:发表于2020-03-23 17:31 被阅读0次

    一、基本介绍

    KVC官网地址

    KVC全称为key value coding,简称键值编码,它是一种机制,使用NSKeyValueCoding协议,定义在Foundation框架的NSKeyValueCoding.h文件中,分别为NSObject、NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet定义了核心的方法,如下所示:

    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    - (id)valueForKey:(NSString *)key;
    

    在OC中,由于除了NSProxy类之外,其它类都直接或间接的继承自NSObject,所以他们分类中定义的方法,就是对NSObject类中方法的重写。

    在NSObject中有一些其它衍生的方法与属性:

    //是否可以直接访问实例变量,默认为YES
    @property (class, readonly) BOOL accessInstanceVariablesDirectly;
    
    //衍生方法,使用keyPath来设置值或者取值
    - (nullable id)valueForKeyPath:(NSString *)keyPath;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    
    //通过一组给定的key值,获取一个key-value形式的字典
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //传入一个key-value形式的字典,自动对对象的属性进行赋值
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    
    //获取value时未找到key调用
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    //设置value时未找到key调用
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    //给key所对应的属性赋空值
    - (void)setNilValueForKey:(NSString *)key;
    

    在KVC的使用中,直接使用属性名作为key,即可完成对属性值的设置与获取:

    [person setValue:@"Tom" forKey:@"name"];
    [person valueForKey:@"name"];
    

    除了对当前对象的属性进行赋值外,还可以对其更“深层”的对象进行赋值。例如对person对象的dog属性的name属性进行赋值。KVC进行多级访问时,直接类似于属性调用一样用点语法进行访问即可。

    [person setValue:@"二哈" forKeyPath:@"dog.name"];
    [person valueForKey:@"dog.name"];
    

    KVC还有一个很强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value。也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给对象的属性赋值。

    //通过一组给定的key值,获取一个key-value形式的字典
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //传入一个key-value形式的字典,自动对对象的属性进行赋值
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    

    在项目中经常会遇到字典转模型的情况,如果在自定义的init方法里逐个赋值,这样每次数据发生改变还需要改赋值语句。然而通过上面的赋值API,可以对数据进行批量赋值。若有返回的key值是我们的关键字需要改变的情况,我们可以在setValue:forUndefinedKey:中进行处理,比如常见的id:

    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        if ([key isEqualToString:@"id"]) {
            self.Id = [value integerValue];
        }
    }
    

    二、KVC的赋值过程

    • KVC赋值要经过如下步骤:
    1. 先按照顺序查找相关方法 set<key>、_set<key>、setIs<key>,若找到,直接调用方法进行赋值;
    2. 若第1步没有找到任何一个相关方法,则查看+ (BOOL)accessInstanceVariablesDirectly方法,判断是否可以直接访问成员变量,这个方法的默认返回为YES;
    3. 如果第2步判断为NO,直接执行KVC的- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key方法,如果我们没有对这个方法进行重写,或者是重写了这个方法,但是要查找的key在这个方法中未进行合理处理,那么系统会抛出一个异常,提示未定义key
    4. 如果第2步返回为YES,继续按照顺序查找相关成员变量_<key>、_is<key>、<key>、<isKey>,如果查找到,对相应成员变量进行赋值
    5. 如果经过第4步仍然未查找到,执行KVC的- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key方法,如果我们没有对这个方法进行重写,或者是重写了这个方法,但是要查找的key在这个方法中未进行合理处理,那么系统会抛出一个异常,提示未定义key
      我们可以按照步骤对KVC的赋值过程进行自定义:
    - (void)lw_setValue:(id)value forKey:(NSString *)key{
        //第一步 判空
        if (key == nil || key.length == 0) {
            return;
        }
        //第二步,按照步骤寻找方法,步骤为set<key>、_set<key>、setIs<key>
        NSString *key_captial = key.capitalizedString;
        NSString *setKey = [NSString stringWithFormat:@"set%@",key_captial];
        if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
            [self performSelector:NSSelectorFromString(setKey) withObject:value];
            return;
        }
        NSString *_setKey = [NSString stringWithFormat:@"_set%@",key_captial];
        if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
            [self performSelector:NSSelectorFromString(_setKey) withObject:value];
            return;
        }
        NSString *setIsKey = [NSString stringWithFormat:@"setIs%@",key_captial];
        if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
            [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
            return;
        }
        //第三步 判断是否可以访问实例变量
        if ([self.class accessInstanceVariablesDirectly] == false) {
            [self setValue:value forUndefinedKey:key];
            return;
        }
        //第四步 寻找实例变量 顺序为_<key>、_is<Key>、<key>、is<key>
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(self.class, &count);
        NSMutableArray <NSString *> *ivarNamesArray = [NSMutableArray <NSString *> arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++) {
            Ivar ivar = ivars[i];
            const char *ivarName = ivar_getName(ivar);
            NSString *ivarName_OC = [NSString stringWithUTF8String:ivarName];
            [ivarNamesArray addObject:ivarName_OC];
        }
        free(ivars);
        //1._<key>
        NSString *ivarKey = [NSString stringWithFormat:@"_%@",key];
        if ([ivarNamesArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            object_setIvar(self, ivar, value);
            return;
        }
        //2._is<Key>
        ivarKey = [NSString stringWithFormat:@"_is%@",key_captial];
        if ([ivarNamesArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            object_setIvar(self, ivar, value);
            return;
        }
        //3.<key>
        ivarKey = key;
        if ([ivarNamesArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            object_setIvar(self, ivar, value);
            return;
        }
        //4.is<key>
        ivarKey = [NSString stringWithFormat:@"is%@",key_captial];
        if ([ivarNamesArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            object_setIvar(self, ivar, value);
            return;
        }
        [self setValue:value forUndefinedKey:key];  
    }
    

    三、KVC的取值过程(这里只针对普通属性,完整版在第五节讲)

    • KVC进行取值要经过如下步骤:
    1. 先按照顺序查找相关方法get<key>、<key>、is<Key>, _<key>方法,如果查找到,调用相应方法返回值
    2. 若第1步没有找到任何一个相关方法,则查看+ (BOOL)accessInstanceVariablesDirectly方法,判断是否可以直接访问成员变量,这个方法的默认返回为YES;
    3. 如果第2步判断为NO,直接执行KVC的- (nullable id)valueForUndefinedKey:(NSString *)key方法,如果我们没有对这个方法进行重写,或者是重写了这个方法,但是要查找的key在这个方法中未进行合理处理,那么系统会抛出一个异常,提示未定义key
    4. 如果第2步返回的是YES,继续按照顺序查找相关成员变量_<key>、_is<key>、<key>、<isKey>,如果查找到,返回相应成员变量的值
    5. 如果经过第4步仍然未查找到,执行KVC的- (nullable id)valueForUndefinedKey:(NSString *)key方法,如果我们没有对这个方法进行重写,或者是重写了这个方法,但是要查找的key在这个方法中未进行合理处理,那么系统会抛出一个异常,提示未定义key
    - (id)lw_valueForKey:(NSString *)key{
        //第一步 判空
        if (key == nil || key.length == 0) {
            return nil;
        }
        //第二步 查找方法 步骤为get<key>、<key>、is<Key>, _<key>
        NSString *key_captial = key.capitalizedString;
        NSString *getKey = [NSString stringWithFormat:@"get%@",key_captial];
        if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
            return [self performSelector:NSSelectorFromString(getKey)];
        }
        if ([self respondsToSelector:NSSelectorFromString(key)]) {
            return [self performSelector:NSSelectorFromString(key)];
        }
        NSString *isKey = [NSString stringWithFormat:@"is%@",key_captial];
        if ([self respondsToSelector:NSSelectorFromString(isKey)]) {
            return [self performSelector:NSSelectorFromString(isKey)];
        }
        NSString *_key = [NSString stringWithFormat:@"_%@",key];
        if ([self respondsToSelector:NSSelectorFromString(_key)]) {
            return [self performSelector:NSSelectorFromString(_key)];
        }
        
        
        //第三步,判断是否可以访问实例变量
        if ([self.class accessInstanceVariablesDirectly] == false) {
            return [self valueForUndefinedKey:key];
        }
        //第四步 查找实例变量
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(self.class, &count);
        NSMutableArray <NSString *> *ivarNameArray = [NSMutableArray <NSString *> arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++) {
            Ivar ivar = ivars[i];
            const char *ivarName = ivar_getName(ivar);
            NSString *ivarName_OC = [NSString stringWithUTF8String:ivarName];
            [ivarNameArray addObject:ivarName_OC];
        }
        free(ivars);
        //1._<key>
        NSString *ivarKey = [NSString stringWithFormat:@"_%@",key];
        if ([ivarNameArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            return object_getIvar(self, ivar);
        }
        //2._is<key>
        ivarKey = [NSString stringWithFormat:@"_is%@",key_captial];
        if ([ivarNameArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            return object_getIvar(self, ivar);
        }
        //3.key
        ivarKey = key;
        if ([ivarNameArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            return object_getIvar(self, ivar);
        }
        //4.isKey
        ivarKey = [NSString stringWithFormat:@"is%@",key_captial];
        if ([ivarNameArray containsObject:ivarKey]) {
            Ivar ivar = class_getInstanceVariable(self.class, ivarKey.UTF8String);
            return object_getIvar(self, ivar);
        }
        return [self valueForUndefinedKey:key];
    }
    

    三、KVC的异常处理与正确性验证

    异常处理

    KVC中的异常主要有三个:

    1. key存在,但value为nil,这种主要针对非对象类型(数字类型、结构体等),这些类型的值不能为空
    2. 赋值的时候,没找到key值
    3. 取值的时候,key值不存在
      针对以上三种情况,我们可以在NSObject的分类中将这三个方法重写,并且使用断言的办法,让它抛出异常,而且也可以让上线后的release版本不会因为这些原因而崩溃
    //MARK: 异常处理
    //这个方法主要针对非对象类型(数字类型、结构体等),这些类型的值不能为空
    - (void)setNilValueForKey:(NSString *)key{
        NSString *description = [NSString stringWithFormat:@"类:%@,方法:%s,%@值不存在",NSStringFromClass(self.class),__func__,key];
        NSAssert(false, description);
    }
    //赋值 没找到key值
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key{
        NSString *description = [NSString stringWithFormat:@"类:%@,方法:%s,kvc赋值未找到key值:%@",NSStringFromClass(self.class),__func__,key];
        NSAssert(false, description);
    }
    //取值 key值不存在
    - (id)valueForUndefinedKey:(NSString *)key{
        NSString *description = [NSString stringWithFormat:@"类:%@,方法:%s,kvc取值未找到key值:%@",NSStringFromClass(self.class),__func__,key];
        NSAssert(false, description);
        return nil;
    }
    

    正确性验证

    NSObject默认给我们提供了两个方法做KVC的正确性验证,一个是key,一个是keyPath,如果我们没有重写这两个方法的话,默认的返回值为YES,也就是不做正确性验证,默认都是正确

    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError;
    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError *__autoreleasing  _Nullable *)outError
    

    该方法的工作原理:

    1. 先找一下类中是否实现了方法
      -(BOOL)validate<Key>:error:
    2. 如果实现了就会根据实现方法里面的自定义逻辑返回NO或者YES,如果没有实现这个方法,则系统默认返回就是YES

    在使用的时候,我们直接使用属性名来代替方法中的key直接对某一个属性进行判断,示例如下:

    @interface LWPerson : NSObject
    
    @property (nonatomic,copy) NSString *name;
    
    @property (nonatomic,assign) NSInteger age;
    
    @property (nonatomic,copy) NSString *nickName;
    
    @property (nonatomic,assign) double height;
    
    @end
    @implementation LWPerson
    - (BOOL)validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError *__autoreleasing  _Nullable *)outError{
        NSNumber *ageNumber = (NSNumber *)(*ioValue);
        NSInteger age = ageNumber.integerValue;
        if (age < 0 || age > 150) {
            NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteNoPermissionError userInfo:@{NSLocalizedDescriptionKey:@"年龄不能小于0,并且不能大于150"}];
            *outError = error;
            return NO;
        }
        return YES;
    }
    @end
    
    //在外部调用
    - (void)validateTest{
        LWPerson *person = [LWPerson new];
        NSNumber *age = @200;
        NSError *error = nil;
        if ([person validateValue:&age forKey:@"age" error:&error] == YES) {
            [person setValue:age forKey:@"age"];
        } else {
            NSLog(@"error:%@",error.localizedDescription);//error:年龄不能小于0,并且不能大于150
        }
    }
    

    四、KVC与集合对象

    1.KVC与字典

    在基本介绍中我们对KVC与字典已经有了一点了解,现在我们用示例来看

    //字典 对象和字典赋值互转
    - (void)dictionaryTest{
        LWPerson *person = [LWPerson new];
        //字典
    //    NSDictionary *dictionary = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@187.5};
        NSDictionary *dictionary = @{@"age":@18,@"nickName":@"Cat",@"height":@187.5};
        [person setValuesForKeysWithDictionary:dictionary];
        NSLog(@"name=%@,age=%ld,nickName=%@,height=%.2lf",person.name,person.age,person.nickName,person.height);//name=(null),age=18,nickName=Cat,height=187.50
        
        NSArray *keys = @[@"name",@"age"];
        //这里的key值对应的value如果是nil的话,字典内存放的是[NSNull null]对象
        dictionary = [person dictionaryWithValuesForKeys:keys];
        NSLog(@"dic:%@",dictionary);//{age = 18;name = "<null>";}
    }
    

    2、KVC的消息传递

    对集合对象使用KVC消息传递,相当于对每个对象传递了一个消息,并且把消息结果通过集合返回来,我们使用数组来举例

    //KVC消息传递 array 使用valueforkey相当于对每个成员发送了一个消息
    - (void)arrayTest{
    //    NSString *string = @"";
    //    string.lowercaseString
        NSArray *array = @[@"Monday",@"Tuesday",@"wednesday"];
        //相当于每个成员调用了length属性
        NSArray *lengthArray = [array valueForKey:@"length"];
        NSLog(@"length:%@",lengthArray);//(6,7,9)
        NSArray *lowercaseStringArray = [array valueForKey:@"lowercaseString"];
        NSLog(@"lower:%@",lowercaseStringArray);//(monday,tuesday,wednesday)
    }
    

    3.KVC容器操作

    一个集合/数组通过调用valueForKeyPath:可允许一个集合中的对象属性根据集合操作符做相应的操作。集合操作符是一个以@开头特殊的字符串,下面是官方文档中关于集合操作的格式说明:

    • Aggregation Operators(聚合运算符)
      聚合运算符可以应该用在数组或者集合上,用于反映集合中某个方面的值的特性
      聚合运算符不支持自定义,它有@avg平均数 @count个数 @max最大值 @sum求和 @min最小值 这五种,除了@count可以不用加属性名外,其它的操作符都必须设置属性
    @interface LWPerson : NSObject
    
    @property (nonatomic,copy) NSString *name;
    
    @property (nonatomic,assign) NSInteger age;
    
    @property (nonatomic,copy) NSString *nickName;
    
    @property (nonatomic,assign) double height;
    
    @end
    @implementation LWPerson
    @end
    ///聚合操作符 不支持自定义 只支持如下五种 @avg平均数 @count个数 @max最大值 @sum求和 @min最小值
    - (void)containerTest{
        NSMutableArray *students = [NSMutableArray array];
        for (NSInteger i = 0; i < 6; i++) {
            LWPerson *person = [LWPerson new];
            //给个正负号
            int sign = arc4random_uniform(2) == 0 ? 1 : -1;
            NSDictionary *dic = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@(170+sign*arc4random_uniform(10))};
            [person setValuesForKeysWithDictionary:dic];
            [students addObject:person];
        }
        //这里是消息传递 不是聚合
        NSLog(@"%@",[students valueForKey:@"height"]);
        /*
         结果
         (
             170,
             166,
             168,
             177,
             164,
             169
         )
         */
        
        
        /*
         下面使用聚合
         官方文档上的格式为 keypathToCollection.@colletionOperator.keypathToProperty
         翻译过来就是 集合的keypath.@操作符.集合成员的属性  (一般集合的keypath是用不到的)
         
         */
        //平均身高
        double height_avarage = [[students valueForKeyPath:@"@avg.height"] doubleValue];
        NSLog(@"平均身高为:%lf",height_avarage);//平均身高为:169.000000
        //个数
        NSInteger height_count = [[students valueForKeyPath:@"@count.height"] integerValue];
        NSLog(@"个数为:%ld",height_count);//个数为:6
        //最大值
        double height_max = [[students valueForKeyPath:@"@max.height"]doubleValue];
        NSLog(@"身高最高为:%.1lf",height_max);//身高最高为:177.0
        //最小值
        double height_min = [[students valueForKeyPath:@"@min.height"]doubleValue];
        NSLog(@"身高最低为:%.1lf",height_min);//身高最低为:164.0
        //总和
        double height_sum = [[students valueForKeyPath:@"@sum.height"]doubleValue];
        NSLog(@"身高之和为:%.1lf",height_sum);//身高之和为:1014.0
    }
    
    • Array Operators(数组操作符)
      数组运算符只作用于数组,只有@distinctUnionOfObjects(去重) @unionOfObjects(不去重)这两个,不去重实际就只是将某个属性值给全提取出来而已
    @interface LWPerson : NSObject
    
    @property (nonatomic,copy) NSString *name;
    
    @property (nonatomic,assign) NSInteger age;
    
    @property (nonatomic,copy) NSString *nickName;
    
    @property (nonatomic,assign) double height;
    
    @end
    @implementation LWPerson
    @end
    ///数组操作符 @distinctUnionOfObjects(去重)  @unionOfObjects(不去重)
    - (void)containerArrayTest{
        NSMutableArray *students = [NSMutableArray array];
        for (NSInteger i = 0; i < 6; i++) {
            LWPerson *person = [LWPerson new];
            //给个符号
            int sign = arc4random_uniform(2) == 0 ? 1 : -1;
            NSDictionary *dic = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@(170+sign*arc4random_uniform(10))};
            [person setValuesForKeysWithDictionary:dic];
            [students addObject:person];
        }
        //这里是消息传递 不是聚合
        NSLog(@"%@",[students valueForKey:@"height"]);
        /*
         结果
         (
             170,
             177,
             178,
             178,
             177,
             175
         )
         */
        
        //对height进行去重
        NSArray *distinctUnionArray = [students valueForKeyPath:@"@distinctUnionOfObjects.height"];
        NSLog(@"distinctUnion:%@,count:%ld",distinctUnionArray,(long)distinctUnionArray.count);
        /*
         结果
         distinctUnion:(
             175,
             178,
             170,
             177
         ),count:4
         */
        //对height进行不去重
        NSArray *unionArray = [students valueForKeyPath:@"@unionOfObjects.height"];
        NSLog(@"union:%@,count:%ld",unionArray,(long)unionArray.count);
        /*
         union:(
             170,
             177,
             178,
             178,
             177,
             175
         ),count:6
         */
    }
    

    注意:
    如果要取的属性在数组内某个对象中值为nil的话,使用数组运算符时会抛出异常

    • Nesting Operators(嵌套运算符)
      嵌套运算符作用于嵌套集合,嵌套集合的每一项都是一个集合。它有@distinctUnionOfArrays(去重) @unionOfArrays(不去重) @distinctUnionOfSets这三个操作符
    ///嵌套集合(array&set)操作  @distinctUnionOfArrays(去重)  @unionOfArrays(不去重) @distinctUnionOfSets
    - (void)nestingArrayTest{
        /*
         注意:
         The valueForKeyPath: method raises an exception if any of the leaf objects is nil when using nesting operators.
         使用嵌套运算符时,如果任何一对象为nil,valueForKeyPath:方法将引发异常。
         */
        NSMutableArray *students0 = [NSMutableArray array];
        for (NSInteger i = 0; i < 6; i++) {
            LWPerson *person = [LWPerson new];
            //给个符号
            int sign = arc4random_uniform(2) == 0 ? 1 : -1;
            NSDictionary *dic = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@(170+sign*arc4random_uniform(10))};
            [person setValuesForKeysWithDictionary:dic];
            [students0 addObject:person];
        }
        //这里是消息传递 不是聚合
        NSLog(@"%@",[students0 valueForKey:@"height"]);
        /*
         (
             173,
             167,
             172,
             172,
             178,
             167
         )
         */
        NSMutableArray *students1 = [NSMutableArray array];
        for (NSInteger i = 0; i < 6; i++) {
            LWPerson *person = [LWPerson new];
            //给个符号
            int sign = arc4random_uniform(2) == 0 ? 1 : -1;
            NSDictionary *dic = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@(170+sign*arc4random_uniform(10))};
            [person setValuesForKeysWithDictionary:dic];
            [students1 addObject:person];
        }
        //这里是消息传递 不是聚合
        NSLog(@"%@",[students1 valueForKey:@"height"]);
        /*
         (
             178,
             169,
             174,
             173,
             178,
             177
         )
         */
        
        //嵌套数组
        NSArray *nestingArray = @[students0,students1];
        
        //全部提取合并并去重
        NSArray *distinctUnionOfArrays = [nestingArray valueForKeyPath:@"@distinctUnionOfArrays.height"];
        NSLog(@"distinctUnionOfArrays:%@",distinctUnionOfArrays);
        /*
         distinctUnionOfArrays:(
             178,
             174,
             172,
             177,
             173,
             169,
             167
         )
         */
        //全部合并不去重
        NSArray *unionOfArrays = [nestingArray valueForKeyPath:@"@unionOfArrays.height"];
        NSLog(@"unionOfArrays:%@",unionOfArrays);
        /*
         unionOfArrays:(
             173,
             167,
             172,
             172,
             178,
             167,
             178,
             169,
             174,
             173,
             178,
             177
         )
         */
        
    }
    - (void)nestingSetTest{
        /*
         注意:
         The valueForKeyPath: method raises an exception if any of the leaf objects is nil when using nesting operators.
         使用嵌套运算符时,如果任何一对象为nil,valueForKeyPath:方法将引发异常。
         */
        NSMutableSet *students0 = [NSMutableSet set];
        for (NSInteger i = 0; i < 6; i++) {
            LWPerson *person = [LWPerson new];
            //给个符号
            int sign = arc4random_uniform(2) == 0 ? 1 : -1;
            NSDictionary *dic = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@(170+sign*arc4random_uniform(10))};
            [person setValuesForKeysWithDictionary:dic];
            [students0 addObject:person];
        }
        //这里是消息传递 set会自动去重
        NSLog(@"%@",[students0 valueForKey:@"height"]);
        /*
         {(
             168,
             175,
             164,
             176
         )}
         */
        NSMutableSet *students1 = [NSMutableSet set];
        for (NSInteger i = 0; i < 6; i++) {
            LWPerson *person = [LWPerson new];
            //给个符号
            int sign = arc4random_uniform(2) == 0 ? 1 : -1;
            NSDictionary *dic = @{@"name":@"Tom",@"age":@18,@"nickName":@"Cat",@"height":@(170+sign*arc4random_uniform(10))};
            [person setValuesForKeysWithDictionary:dic];
            [students1 addObject:person];
        }
        //这里是消息传递 set会自动去重
        NSLog(@"%@",[students1 valueForKey:@"height"]);
        /*
         {(
             165,
             168,
             174,
             172,
             179
         )}
         */
        
        //嵌套集合
        NSSet *nestingSet = [NSSet setWithObjects:students0,students1, nil];
        
        //全部提取合并并去重
        NSArray *distinctUnionOfSets = [nestingSet valueForKeyPath:@"@distinctUnionOfSets.height"];
        NSLog(@"distinctUnionOfSets:%@",distinctUnionOfSets);
        /*
         distinctUnionOfSets:{(
             165,
             174,
             179,
             175,
             176,
             172,
             168,
             164
         )}
         */
        
    }
    

    注意:
    如果要取的属性在某个对象中值为nil的话,使用嵌套运算符时会抛出异常

    五、KVC取值完整版(集合代理对象)

    官网上关于KVC完整的取值过程如下图:

    KVC完整取值过程

    翻译过来就是

    1. 先按照顺序查找相关方法get<key>、<key>、is<Key>, _<key>方法,如果查找到,则调用它并继续跳转到步骤5执行并返回结果。否则继续下一步。
    2. 如果在1中没找到对应方法,在实例中搜索名称与countOf<Key>和objectIn<Key>AtIndex:(对应于NSArray类中定义的基本方法)和<Key>AtIndex:(对应于NSArray方法objectsAtIndexes:)匹配的方法。如果找到其中的第一个和其他两个方法中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则,继续执行步骤3。
    3. 如果找不到简单的访问器方法或数组访问的方法组,查找名为countOf<Key>、enumeratorOf<Key>和memberOf<Key>的三个方法:(对应于NSSet类定义的基元方法)。如果找到这三个方法,则创建一个集合代理对象,该对象响应所有NSSet方法并返回该对象。否则,继续执行步骤4。此代理对象随后将接收到的任何NSSet消息转换为countOf<Key>、enumeratorOf<Key>和memberOf<Key>:消息的组合,并将其转换为创建它的对象。实际上,代理对象与键值编码兼容对象一起工作,允许底层属性的行为如同它是NSSet,即使它不是NSSet。
    4. 如果找不到简单的访问器方法或集合访问方法组,则查看+ (BOOL)accessInstanceVariablesDirectly方法,判断是否可以直接访问成员变量,这个方法的默认返回为YES。如返回为YES,则查看+ (BOOL)accessInstanceVariablesDirectly方法,判断是否可以直接访问成员变量,这个方法的默认返回为YES;如果为YES的话,继续按照顺序查找相关成员变量_<key>、_is<key>、<key>、<isKey>,如果查找到,返回相应成员变量的值;如果返回为NO,则直接跳到第六步
    5. 如果检索到的属性值是对象,则只需返回结果;如果该值是NSNumber支持的标量类型,将其存储在NSNumber实例中并返回该实例。如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回
    6. 如果之前的查找都没找到,执行KVC的- (nullable id)valueForUndefinedKey:(NSString *)key方法,如果我们没有对这个方法进行重写,或者是重写了这个方法,但是要查找的key在这个方法中未进行合理处理,那么系统会抛出一个异常,提示未定义key

    我们主要针对集合代理对(第2步和第3步)象来进行举例:

    @interface LWPerson : NSObject
    
    @property (nonatomic,assign) NSUInteger count;
    
    @property (nonatomic,strong) NSArray *penArray;
     
    @end
    
    @implementation LWPerson
    ///返回数组 key为books
    //首先查找这个方法 下面objectInBooksAtIndex:与booksAtIndexes:二选一
    - (NSUInteger)countOfBooks{
        NSLog(@"%s",__func__);
        return self.count;
    }
    - (id)objectInBooksAtIndex:(NSUInteger)index{
        NSLog(@"%s",__func__);
        return [NSString stringWithFormat:@"book %lu",index+1];
    }
    - (NSArray *)booksAtIndexes:(NSIndexSet *)indexes{
        NSLog(@"%s",__func__);
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:indexes.count];
        [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
            [mutableArray addObject:[NSString stringWithFormat:@"%lu book",idx]];
        }];
        return mutableArray.copy;
    }
    
    ///返回set key为pens
    //按照步骤分别查找这三个方法
    - (NSUInteger)countOfPens{
        NSLog(@"%s",__func__);
        return self.penArray.count;
    }
    
    - (id)memberOfPens:(id)object{
        NSLog(@"%s",__func__);
        return [self.penArray containsObject:object] ? object : nil;
    }
    - (NSEnumerator *)enumeratorOfPens{
        NSLog(@"%s",__func__);
        return [self.penArray objectEnumerator];
    }
    
    @end
    
    //在外面调用
    - (void)collectionProxyObject{
        ///数组测试
        LWPerson* p = [LWPerson new];
        p.count = 5;
        NSArray *booksArray = [p valueForKey:@"books"];
        NSLog(@"books = %@", booksArray);
        
        ///Set测试
        p.penArray = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
        NSSet* pensSet = [p valueForKey:@"pens"];
        NSLog(@"pens = %@", pensSet);
        NSEnumerator* enumerator = [pensSet objectEnumerator];
        NSString* str = nil;
        while (str = [enumerator nextObject]) {
            NSLog(@"%@", str);
        }
    }
    
    结果打印:
    -[LWPerson countOfBooks]
    -[LWPerson countOfBooks]
    -[LWPerson objectInBooksAtIndex:]
    -[LWPerson objectInBooksAtIndex:]
    -[LWPerson objectInBooksAtIndex:]
    -[LWPerson objectInBooksAtIndex:]
    -[LWPerson objectInBooksAtIndex:]
    books = (
        "book 1",
        "book 2",
        "book 3",
        "book 4",
        "book 5"
    )
    -[LWPerson countOfPens]
    -[LWPerson countOfPens]
    -[LWPerson enumeratorOfPens]
    pens = {(
        pen0,
        pen1,
        pen2,
        pen3
    )}
    -[LWPerson enumeratorOfPens]
    pen0
    pen1
    pen2
    pen3
    
    

    上例中可以看出

    • 我们在查找books这个key时,首先调用- (NSUInteger)countOfBooks,然后再查看有- (id)objectInBooksAtIndex:(NSUInteger)index方法,如果有,根据之前返回的count调用count次来返回数组对象,如果没有找到- (id)objectInBooksAtIndex:(NSUInteger)index方法,它将会查找- (NSArray *)booksAtIndexes:(NSIndexSet *)indexes方法来返回整个数组
    • 在查找pens这个key时,依次调用countOfPens和enumeratorOfPens来进行输出,没有看到调用memberOfPens:这个方法,这个不知道是官网文档错误还是我的错误,希望有大神可以解答

    相关文章

      网友评论

          本文标题:iOS之KVC

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