美文网首页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

    KVC(NSKeyValueCoding) 常用的KVC方法:setValue: forKey:与setValue...

  • KVC与NSKeyValueCoding

    KVC全称是Key Value Coding,在NSKeyValueCoding.h非正式协议文件中,声明了KVC...

  • kvc

    kvc 底层驱动NSKeyValueCoding 协议 NSKeyValueCoding Getting Valu...

  • KVC(NSKeyValueCoding):

    setValue:属性值 forKey:属性名: 为指定属性设置值 valueForKey:属性名: 获取指定属性...

  • iOS面试题之 KVO KVC MVC

    KVC 与 KVO 1,KVC:NSKeyValueCoding 键值编码:是一种用字符串间接访问对象属性的机制....

  • KVC-NSKeyValueCoding

    KVC作为OC中重要的编程思想、在很多场景下我们都会使用,也是开发者必须掌握的基础知识。那么什么是KVC,他的实现...

  • KVC原理分析

    一、KVC简介   KVC(Key-Value Coding)键值编码,是利用NSKeyValueCoding 非...

  • ReactiveCocoa 前奏 KVC. KVO. NSNot

    KVC KVC (key-value observing) 是基于 NSKeyValueCoding 的一个非正式...

  • KVC&KVO

    KVC Key value coding 键值对编程OC中KVC相关方法都放在NSKeyValueCoding类别...

  • OC-KVC

    KVC是什么 kvc就是Key-Value Coding(键值编码)。 键值编码是NSKeyValueCoding...

网友评论

    本文标题:KVC与NSKeyValueCoding

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