KVC探讨-具体应用以及属性验证、异常处理(三)

2020-03-01  本文已影响0人  GitArtOS

1.基本用法

直接对属性或者成员变量进行取值和赋值

    GTPerson *person = [[GTPerson alloc] init];
    [person setValue:@"GT" forKey:@"name"];
    [person setValue:@29 forKey:@"age"];
    [person setValue:@"GT" forKey:@"myName"];
    NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);

2. 操作集合类型

针对集合属性,可以直接通过mutableArrayValueForKey对对象的集合属性进行操作更改

    person.array = @[@"1",@"2",@"3"];
    // KVC 的方式
    NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
    ma[0] = @"100";
    NSLog(@"%@",[person valueForKey:@"array"]);

3. 访问非对象属性

3.1 标量属性
标量属性.png

如图所示,常用的基本数据类型需要在设置属性的时候包装成NSNumber类型,然后在读取值的时候使用各自对应的读取方法,如 double 类型的标量读取的时候使用 doubleValue;

3.2 结构体
结构体.png

针对结构体,KVC也可以直接操作,但是操作时候需要将结构体转成NSValue类型;结构体的话就需要转换成 NSValue类型,如上图所示。 除了NSPoint, NSRange, NSRect, 和 NSSize,对于自定义的结构体,也需要进行 NSValue的转换操作

typedef struct {
    float x, y, z;
} ThreeFloats;

    ThreeFloats floats = {1., 2., 3.};
    NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
    [person setValue:value forKey:@"threeFloats"];
    NSValue *reslut = [person valueForKey:@"threeFloats"];
    NSLog(@"%@",reslut);
    
    ThreeFloats th;
    [reslut getValue:&th] ;
    NSLog(@"%f - %f - %f",th.x,th.y,th.z);

4.嵌套访问

假如对象的属性也是对象,那么KVC可以通过keyPath来操作对象属性的属性

    GTStudent *student = [[GTStudent alloc] init];
    student.subject    = @"iOSGG";
    person.student     = student;
    [person setValue:@"GTOS" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);

5.集合操作符

5.1字典操作

假如字典的key和一个对象的属性都一样,那么可以通过setValuesForKeysWithDictionary直接将字典的value赋值给对象相应的属性,同样,也可以通过dictionaryWithValuesForKeys将对象转换成字典

- (void)dictionaryTest{
    
    NSDictionary* dict = @{
                           @"name":@"Cooci",
                           @"nick":@"GitArtOS",
                           @"subject":@"iOSGG",
                           @"age":@29,
                           @"length":@120
                           };
    GTStudent *p = [[GTStudent alloc] init];
    // 字典转模型
    [p setValuesForKeysWithDictionary:dict];
    NSLog(@"%@:%@",p,p.name);
    // 键数组转模型到字典
    NSArray *array = @[@"name",@"age"];
    NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
    NSLog(@"%@",dic);
}

5.2 操作数组元素的信息(KVC消息传递)

通过api可以拿到数组元素的长度,也可以对数组元素进行操作得到新的数组

NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC"];
    //得到数组中所有元素的长度
    NSArray *lenStr= [array valueForKeyPath:@"length"];
    NSLog(@"%@",lenStr);
    //将数组中所有值全变成小写
    NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
    NSLog(@"%@",lowStr);
    //将数组中所有值全变成大写
    NSArray *uppercaseStr= [array valueForKeyPath:@"uppercaseString"];
    NSLog(@"%@",uppercaseStr);
5.3 聚合操作符

@avg:取平均值 @count:取个数 @max:取最大值 @min:取最小值 @sum:求和

- (void)aggregationOperator{
    NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        LGStudent *p = [LGStudent new];
        NSDictionary* dict = @{
                               @"name":@"GitArtOS",
                               @"age":@(28+i),
                               @"nick":@"GTOS",
                               @"length":@(115 + 2*arc4random_uniform(6)),
                               };
        [p setValuesForKeysWithDictionary:dict];
        [personArray addObject:p];
    }
    NSLog(@"%@", [personArray valueForKey:@"length"]);
    
    /// 平均身高
    float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
    NSLog(@"%f", avg);
    
    int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
    NSLog(@"%d", count);
    
    int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
    NSLog(@"%d", sum);
    
    int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
    NSLog(@"%d", max);
    
    int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
    NSLog(@"%d", min);
}

5.4 数组操作符

可以做去做相应的去重等操作

- (void)arrayOperator{
    NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        GTStudent *p = [GTStudent new];
        NSDictionary* dict = @{
                               @"name":@"GitArtOS",
                               @"age":@(28+i),
                               @"nick":@"GTOS",
                               @"length":@(115 + 2*arc4random_uniform(6)),
                               };
        [p setValuesForKeysWithDictionary:dict];
        [personArray addObject:p];
    }
    NSLog(@"%@", [personArray valueForKey:@"length"]);
    // 返回操作对象指定属性的集合
    NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.nick"];
    NSLog(@"arr1 = %@", arr1);
    // 返回操作对象指定属性的集合 -- 去重
    NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
    NSLog(@"arr2 = %@", arr2);
    
}

5.5 嵌套操作

@distinctUnionOfArrays:去重取值 @unionOfArrays:取值

- (void)arrayNesting{
    
    NSMutableArray *personArray1 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        GTStudent *student = [GTStudent new];
        NSDictionary* dict = @{
                            @"name":@"GitArtOS",
                               @"age":@(28+i),
                               @"nick":@"GTOS",
                               @"length":@(115 + 2*arc4random_uniform(6)),
                               };
        [student setValuesForKeysWithDictionary:dict];
        [personArray1 addObject:student];
    }
    
    NSMutableArray *personArray2 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        GTPerson *person = [GTPerson new];
        NSDictionary* dict = @{
                            @"name":@"GitArtOS",
                               @"age":@(28+i),
                               @"nick":@"GTOS",
                               @"length":@(115 + 2*arc4random_uniform(6)),
                               };
        [person setValuesForKeysWithDictionary:dict];
        [personArray2 addObject:person];
    }
    
    // 嵌套数组
    NSArray* nestArr = @[personArray1, personArray2];
    
    NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.length"];
    NSLog(@"arr = %@", arr);
    
    NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.length"];
    NSLog(@"arr1 = %@", arr1);
}


5.6 嵌套操作另一种
- (void)setNesting{
    
    NSMutableSet *personSet1 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        GTStudent *person = [GTStudent new];
        NSDictionary* dict = @{
                            @"name":@"GitArtOS",
                               @"age":@(28+i),
                               @"nick":@"GTOS",
                               @"length":@(115 + 2*arc4random_uniform(6)),
                               };
        [person setValuesForKeysWithDictionary:dict];
        [personSet1 addObject:person];
    }
    NSLog(@"personSet1 = %@", [personSet1 valueForKey:@"length"]);
    
    NSMutableSet *personSet2 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        GTStudent *person = [GTStudent new];
        NSDictionary* dict = @{
                      @"name":@"GitArtOS",
                               @"age":@(28+i),
                               @"nick":@"GTOS",
                               @"length":@(115 + 2*arc4random_uniform(6)),
                               };
        [person setValuesForKeysWithDictionary:dict];
        [personSet2 addObject:person];
    }
    NSLog(@"personSet2 = %@", [personSet2 valueForKey:@"length"]);

    // 嵌套set
    NSSet* nestSet = [NSSet setWithObjects:personSet1, personSet2, nil];
    // 交集
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.length"];
    NSLog(@"arr1 = %@", arr1);
}

6. 属性验证

KVC 支持属性验证,而这一特性是通过validateValue:forKey:error: (或validateValue:forKeyPath:error:) 方法来实现的。这个验证方法的默认实现是去收到这个验证消息的对象(或keyPath中最后的对象)中根据 key 查找是否有对应的validate<Key>:error:方法实现,如果没有,验证默认成功,返回 YES。
而由于validate<Key>:error: 方法通过引用接收值和错误参数,

所以会有以下三种结果:

GTPerson* person = [[GTPerson alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@"%@",error);
}

那么是否系统会自动进行属性验证呢?
通常,KVC 或其默认实现均未定义任何机制来自动的执行属性验证,也就是说需要在适合你的应用的时候自己提供属性验证方法
某些其他Cocoa技术在某些情况下会自动执行验证。 例如,保存 managed object context 时,Core Data会自动执行验证。另外,在 macOS中,Cocoa Binding允许你指定验证应自动进行。

7 KVC使用的注意事项

在实际过程中, 一个类的成员变量有可能是自定义的类或者其他的复杂类型, 这时候如果想要使用KVC获取到自定义类的属性就会比较麻烦。这时KVC给我们提供了一个解决方案, 键路径 keyPath。方法如下:

- (nullable id)valueForKeyPath:(NSString *)keyPath;  //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值

验证代码

// 
GTStudent *student = [[GTStudent alloc] init];
student.classNumber = @"num1";
student.number = @"029";
person.student = student;
NSLog(@"classNum: %@", [personvalueForKeyPath:@"student.classNumber"]);
// 打印结果
2020-03-01 23:27:08.482381+0800 GTtest[96366:1435734] classNum: num1

上面展示keyPath的简单用法, 此时如果我们调用的方法是 valueForKey:的话, 一般情况下系统会去调用undefinedKey方法, 因为没有找到这个属性及其相关的方法和实例变量。KVC在此方法中的搜索机制首先根据" . " 来分割key, 然后在去按照上面的顺序去搜索下去。

8. KVC 异常处理

8.1 设置空值 nil处理

我们知道KVC有时候会帮我们去自动转换我们所传的值, 但是当我们传nil的时候KVC是怎么处理的呢?

- (void)setNilValueForKey:(NSString *)key;

- (void)setNilValueForKey:(NSString *)key{
    NSLog(@"GT: 设置 %@ 是空值",key);//重写这个方法做nil处理
}
@property (nonatomic, copy) NSString *subject;
@property (nonatomic, assign) int  age;
@property (nonatomic, assign) BOOL sex;
@property (nonatomic) ThreeFloats  threeFloats;

// 测试代码
  NSLog(@"******2: 设置空值******");
  [person setValue:nil forKey:@"age"]; 
  [person setValue:nil forKey:@"subject"];
  [person setValue:nil forKey:@"sex"];
  [person setValue:nil forKey:@"threeFloats"];
// 打印结果
2020-03-01 23:38:44.538117+0800 GTtest[3703:1577245] ******2: 设置空值******
2020-03-01 23:38:44:44.538200+0800 GTtest[3703:1577245] GT: 设置 age 是空值
2020-03-01 23:38:44:44.538293+0800 GTtest[3703:1577245] GT: 设置 sex 是空值
2020-02-16 16:58:44.538487+0800 GTtest[3703:1577245] GT: 设置 threeFloats 是空值

8.2 赋值时候找不到的 key

当对不存在的key进行赋值时候也会崩溃,解决办法时候就是在分类中重写下面方法即可:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"GT: %@ 没有这key",key);
}
8.3 取值时候找不到key

对不存在的key进行取值时候也会崩溃,解决办法就是在分类中重写下面方法:

- (id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"GT: %@ 没有这key - 给你一个其他的吧,别崩溃了!",key);
    return @"NND 牛X";
}

KVC探讨-设定值 setValue: forKey:和取值valueForKey:(一)
KVC探讨-操作数组和集合、字典探讨(二)
KVC探讨-自定义KVC(四)

上一篇 下一篇

猜你喜欢

热点阅读