美文网首页selector
KVC与NSKeyValueCoding

KVC与NSKeyValueCoding

作者: Maj_sunshine | 来源:发表于2018-06-20 14:21 被阅读5次

    KVC全称是Key Value Coding,在NSKeyValueCoding.h非正式协议文件中,声明了KVC能使用的方法,KVC提供一种通过字符串来访问类中的属性和成员变量的方法。

    • NSKeyValueCoding协议中的方法


      NSKeyValueCoding.gif

    从NSKeyValueCoding协议方法说去

    先来看看几个宏
    当调用KVC时key值为空时,就会抛出这个异常。
    FOUNDATION_EXPORT NSExceptionName const NSUndefinedKeyException;
    
    // NSKeyValueCoding中的运算符
    NSKeyValueOperator const NSAverageKeyValueOperator; // 求平均值
    NSKeyValueOperator const NSCountKeyValueOperator; // 统计总数
    NSKeyValueOperator const NSDistinctUnionOfArraysKeyValueOperator; // 获取嵌套数组中不同的值
    NSKeyValueOperator const NSDistinctUnionOfObjectsKeyValueOperator; // 获取不同的值
    NSKeyValueOperator const NSDistinctUnionOfSetsKeyValueOperator; // 获取嵌套集合中不同的值
    NSKeyValueOperator const NSMaximumKeyValueOperator; // 获取最大值
    NSKeyValueOperator const NSMinimumKeyValueOperator; // 获取最小值
    NSKeyValueOperator const NSSumKeyValueOperator; // 求和
    NSKeyValueOperator const NSUnionOfArraysKeyValueOperator; // 获取嵌套数组中的值,不去重
    NSKeyValueOperator const NSUnionOfObjectsKeyValueOperator; // 获取所有的值,不去重
    NSKeyValueOperator const NSUnionOfSetsKeyValueOperator; // 获取嵌套集合中的值,不去重
    

    这些运算符能帮助我们快速的运算集合中的操作。在官方文档中教了我们怎么使用这些操作符。

    NSArray *array = @[@"1",@"1",@"1",@"2",@"2",@"3",@"3",@"4",@"5",@"5"];
        NSArray *array1 = @[@"3",@"4",@"5",@"5",@"6",@"6",@"7",@"8",@"8"];
        NSArray *unionArray = @[array,array1];
    
    • 获取数组的个数,这个一般没有,因为数组就要count属性。
        NSString *count = [array valueForKeyPath:@"@count"];
        NSLog(@"数组个数 = %@",count);
    
    2018-06-19 15:50:20.568834+0800 KVC和NSKeyValueCoding[10417:285376] 数组个数 = 10
    
    • 获取数组的平均值
        NSString *avg = [array valueForKeyPath:@"@avg.self"];
        NSLog(@"数组平均值 = %@",avg);
    
    2018-06-19 15:50:20.569342+0800 KVC和NSKeyValueCoding[10417:285376] 数组平均值 = 2.7
    
    • 获取去重后的数组。如果有一道题目是让我们从一万个重复的数据中找出不同的数,就可以用这个方法。当然也可以数组转NSSet去重,再转NSArray排序。
        NSArray *distinct = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
        NSArray *distinctUnionOfObjects = [distinct sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
            return [obj1 compare:obj2];
        }]; //数组正序
        NSLog(@"去重后的数组 = %@",distinctUnionOfObjects);
    
    2018-06-19 15:50:20.569594+0800 KVC和NSKeyValueCoding[10417:285376] 去重后的数组 = (
        1,
        2,
        3,
        4,
        5
    )
    
    • 数组数字最大值
        NSString *max = [array valueForKeyPath:@"@max.self"];
        NSLog(@"数组数字最大值 = %@",max);
    
    2018-06-19 15:50:20.569777+0800 KVC和NSKeyValueCoding[10417:285376] 数组数字最大值 = 5
    
    • 数组数字最小值
        NSString *min = [array valueForKeyPath:@"@min.self"];
        NSLog(@"数组数字最小值 = %@",min);
    
    2018-06-19 15:50:20.569928+0800 KVC和NSKeyValueCoding[10417:285376] 数组数字最小值 = 1
    
    • 获取数组数字总和
        NSString *sum = [array valueForKeyPath:@"@sum.self"];
        NSLog(@"总和  = %@", sum);
    
    2018-06-19 15:50:20.570135+0800 KVC和NSKeyValueCoding[10417:285376] 总和  = 27
    
    • 不去重的数组,这个在开发中也没用,因为就算数组本身。
        NSArray *unionOfObjects = [array valueForKeyPath:@"@unionOfObjects.self"];
        NSLog(@"不去重的数组 = %@", unionOfObjects);
    
    2018-06-19 15:50:20.570313+0800 KVC和NSKeyValueCoding[10417:285376] 不去重的数组 = (
        1,
        1,
        1,
        2,
        2,
        3,
        3,
        4,
        5,
        5
    )
    
    • 多个数组去重。去重后变一个数组。
        NSArray *distinctUnionOfArrays = [unionArray valueForKeyPath:@"@distinctUnionOfArrays.self"];
        NSLog(@"多个数组去重后 = %@",distinctUnionOfArrays);
    
    2018-06-19 15:50:20.570545+0800 KVC和NSKeyValueCoding[10417:285376] 多个数组去重后 = (
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        1
    )
    
    • 多个数组不去重,这个在开发中被可变数组的addObjectsFromArray方法替代.
        NSArray *unionOfArrays = [unionArray valueForKeyPath:@"@unionOfArrays.self"];
        NSLog(@"多个数组不去重 = %@",unionOfArrays);
    
    2018-06-19 15:50:20.570716+0800 KVC和NSKeyValueCoding[10417:285376] 多个数组不去重 = (
        1,
        1,
        1,
        2,
        2,
        3,
        3,
        4,
        5,
        5,
        3,
        4,
        5,
        5,
        6,
        6,
        7,
        8,
        8
    )
    
    赋值和取值操作
    • 创建一个 Animal 自定义对象
    #import <Foundation/Foundation.h>
    
    @class  Food;
    /**
     对象
     */
    @interface Animal : NSObject
    
     // 动物姓名
    @property (nonatomic, copy) NSString *name;
     // 动物年龄 
    @property (nonatomic, assign) NSInteger age;
     // 食物 
    @property (nonatomic, strong) Food *food;
    
    @end
    
    @interface Food : NSObject
     // 水果
    @property (nonatomic, copy) NSString *fruit;
     // 肉 
    @property (nonatomic, copy) NSString *meat;
    
    @end
    
    #import "Animal.h"
    
    @implementation Animal
    
    - (Food *)food {
        if (!_food) {
            _food = [[Food alloc] init];
        }
        return _food;
    }
    @end
    
    
    @implementation Food
    
    @end
    
    • 使用- setValue: forKey: 赋值,valueForKey:方法取值。直接通过Animal中的属性名来作为key赋值取值。
    [_animal setValue:@"小妹" forKey:@"name"];
    NSLog(@"name = %@",[_animal valueForKey:@"name"]);
    
    2018-06-19 17:13:32.029108+0800 KVC和NSKeyValueCoding[11681:325413] name = 小妹
    
    • 如果想对Animal对象中的Food对象的fruit属性赋值,这种多级访问设置或获取value就要用到keyPath来存取。
     [_animal setValue:@"苹果" forKeyPath:@"food.fruit"];
     NSLog(@"fruit = %@",[_animal valueForKeyPath:@"food.fruit"]);
    
    2018-06-19 17:34:33.650983+0800 KVC和NSKeyValueCoding[11746:338536] fruit = 苹果
    

    如果用- setValue: forKey:存取,则会造成crash

    [_animal setValue:@"苹果" forKey:@"food.fruit"];
    NSLog(@"fruit = %@",[_animal valueForKey:@"food.fruit"]);
    
    Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x60000003cdc0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key food.fruit.'
    
    多值的赋值和取值
    • 使用的方法
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    
    • 例子
        [_animal setValuesForKeysWithDictionary:@{@"name":@"小马哥" , @"age" : @(25)}];
        NSLog(@"name = %@",[_animal valueForKey:@"name"]);
        NSLog(@"age = %li",_animal.age);
        NSLog(@"赋值后为 %@",[_animal dictionaryWithValuesForKeys:@[@"name",@"age"]]);
    
    • 方法输出
    2018-06-20 09:19:00.728542+0800 KVC和NSKeyValueCoding[12597:503862] name = 小马哥
    2018-06-20 09:19:00.728667+0800 KVC和NSKeyValueCoding[12597:503862] age = 25
    2018-06-20 09:19:00.728941+0800 KVC和NSKeyValueCoding[12597:503862] 赋值后为 {
        age = 25;
        name = "\U5c0f\U9a6c\U54e5";
    }
    
    • 项目中,我们字典转模型通常是
     // 初始化赋值
    - (instancetype)initWithDic:(NSDictionary *)dic {
        if (self = [super init]) {
             self.age = [dic[@"age"] integerValue];
             self.name = dic[@"name"];
        }
        return self;
    }
    

    但是当我们知道了多值操作的方法,并且model的属性和字典的key相同,我们可以使用下面方法,减少赋值的多余代码。

     // 初始化赋值
    - (instancetype)initWithDic:(NSDictionary *)dic {
        if (self = [super init]) {
            [self setValuesForKeysWithDictionary:dic];
        }
        return self;
    }
    
    // 重写 setValuesForKeysWithDictionary方法 去除 nil和null
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {
        for (id key in keyedValues.allKeys) {
            if (![keyedValues valueForKey:key]) {
                [self setValue:@"" forKey:key];
            } else if ([[keyedValues valueForKey:key] isEqual:[NSNull null]]) {
                [self setValue:@"" forKey:key];
            } else {
                [self setValue:[keyedValues valueForKey:key] forKey:key];
            }
        }
    }
    
    集合getter

    在对集合对象进行getter操作时,可以调用一下方法通过key或者keyPath获取。如果再对容器类进行addremove等操作时,就会触发KVO的消息通知

    • key
    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    - (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
    - (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
    
    • keypath
    - (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;
    
    属性验证
    • 相对应的也有key和keyPath对应的两个方法。用来验证传入的value和key是否正确。
    - (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 value` 是否满足需求
    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
        if ([inKey isEqualToString:@"age"]) {
            if ([*ioValue integerValue] < 18) {
                *outError = [NSError errorWithDomain:AnimalErrorDomain code:10001 userInfo:@{NSLocalizedDescriptionKey : @"小动物还未成年"}];
                return NO;
            } 
        }
        return YES;
    }
    
    NSNumber *age = @(12);
        NSError *error;
        if ([_animal validateValue:&age forKey:@"age" error:&error]) {
            NSLog(@"小动物成年了");
        } else {
            NSLog(@"error = %@",error);
        }
    
    2018-06-20 09:07:57.458669+0800 KVC和NSKeyValueCoding[12550:497482] error = Error Domain=AnimalErrorDomain Code=10001 "小动物还未成年" UserInfo={NSLocalizedDescription=小动物还未成年}
    

    KVC的底层实现

    为了方便阅读,将实例变量放在了.h文件中。

    • 赋值流程
      1 先调用属性的setter方法完成赋值。
      2 如果没有发现setter方法,则检查+ (BOOL)accessInstanceVariablesDirectly方法,默认返回YES,这是在没有找到存取器的时候才调用的方法。返回为YES表示查找_<key>_is<Key><key>is<Key>value,如果有则赋值。
      3 如果都没有找到,则会调用setValue:forUndefinedKey:方法并抛出异常。

    • 测试
      .h文件

    #import <Foundation/Foundation.h>
    
    @class  Food;
    /**
     对象
     */
    @interface Animal : NSObject {
        NSString *name;
        NSString *_name;
        NSString *_isName;
        NSString *isName;
    }
    
     // 动物姓名
    @property (nonatomic, copy) NSString *name;
    
    @end
    

    .m文件

    #pragma mark --- KVC的底层实现测试
     // 是否可以按照 _<key>、_is<Key>、<key>、is<Key> 方式查找
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    
     // setter
    - (void)setName:(NSString *)name {
         NSLog(@"调用了setter方法");
        _name = name;
    }
    
     // 重写setValue: forKey: 用来查看当前的key。具体是_name,_isName,name,isName.
    - (void)setValue:(id)value forKey:(NSString *)key {
        NSLog(@"%@", [NSString stringWithFormat:@"当前调用的key %@",key]);
        [super setValue:value forKey:key];
    }
    
        // kvc底层实现验证
        Animal *animal = [[Animal alloc] init];
        [animal setValue:@"晓东" forKey:@"name"];
        NSLog(@"name = %@",[animal valueForKey:@"name"]);
    

    打印

    2018-06-20 13:38:45.541011+0800 KVC和NSKeyValueCoding[14274:650523] 当前调用的key name
    2018-06-20 13:38:45.541134+0800 KVC和NSKeyValueCoding[14274:650523] 调用了setter方法
    2018-06-20 13:38:45.541251+0800 KVC和NSKeyValueCoding[14274:650523] name = 晓东
    

    现在把属性name去掉,这样就不会默认生成存取器方法。因为accessInstanceVariablesDirectly为NO,所有会走setValue:(id)value forUndefinedKey:(NSString *)key方法。我们如果不想让外界访问类的成员变量,则可以将accessInstanceVariablesDirectly属性赋值为NO。

    @interface Animal : NSObject {
        NSString *name;
        NSString *_name;
        NSString *_isName;
        NSString *isName;
    }
    
     // 动物姓名
    //@property (nonatomic, copy) NSString *name;
    
    #pragma mark --- KVC的底层实现测试
     // 是否可以按照 _<key>、_is<Key>、<key>、is<Key> 方式查找
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    
     // setter
    //- (void)setName:(NSString *)name {
    //    NSLog(@"调用了setter方法");
    //    _name = name;
    //}
    
     // 重写setValue: forKey: 用来查看当前的key。具体是_name,_isName,name,isName.
    - (void)setValue:(id)value forKey:(NSString *)key {
        NSLog(@"%@", [NSString stringWithFormat:@"当前调用的key %@",key]);
        [super setValue:value forKey:key];
    }
    
    2018-06-20 13:49:54.816830+0800 KVC和NSKeyValueCoding[14428:661962] 当前调用的key name
    2018-06-20 13:49:54.823548+0800 KVC和NSKeyValueCoding[14428:661962] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x604000277500> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
    

    接下来把accessInstanceVariablesDirectly变成YES,因为存在_name,_isName,name,isName四个实例变量,所以会找到相应的key并将value赋值。依次注释操作

    @interface Animal : NSObject {
        NSString *name;
    //    NSString *_name;
    //    NSString *_isName;
    //    NSString *isName;
    }
    
    @interface Animal : NSObject {
    //    NSString *name;
        NSString *_name;
    //    NSString *_isName;
    //    NSString *isName;
    }
    
    @interface Animal : NSObject {
    //    NSString *name;
    //    NSString *_name;
        NSString *_isName;
    //    NSString *isName;
    }
    
    @interface Animal : NSObject {
    //    NSString *name;
    //    NSString *_name;
    //    NSString *_isName;
        NSString *isName;
    }
    

    打印出来的都是

    2018-06-20 13:55:08.241934+0800 KVC和NSKeyValueCoding[14468:666416] 当前调用的key name
    2018-06-20 13:56:40.147144+0800 KVC和NSKeyValueCoding[14497:668042] name = 晓东
    

    当实例变量_name,_isName,name,isName都不存在时并且setter方法不存在,

    NSKeyValueCoding[14428:661962] 当前调用的key name
    2018-06-20 13:49:54.823548+0800 KVC和NSKeyValueCoding[14428:661962] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Animal 0x604000277500> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
    

    错误处理

    • key值的错误
      上面一直报的一个异常setValue:forUndefinedKey:,我们可以通过重写这个方法来停止报这个异常。
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
        NSLog(@"赋值失败,该key不存在%@",key);
    }
    

    重写后打印。

    2018-06-20 14:05:23.053180+0800 KVC和NSKeyValueCoding[14535:674635] 赋值失败,该key不存在name
    

    相关文章

      网友评论

        本文标题:KVC与NSKeyValueCoding

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