ios 底层 KVC原理

619 阅读4分钟

KVC 含义

我们看下官方给的定义:

Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. 【译】KVC 是通过 NSKeyValueCoding 这个非正式协议启用的一种机制,而遵循了这个协议的对象就提供了对其属性的间接访问。

KVC 的用途

1.基本用法

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

    LGPerson *person = [[LGPerson alloc] init];
    [person setValue:@"KC" forKey:@"name"];
    [person setValue:@19 forKey:@"age"];
    [person setValue:@"酷C" 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.访问非对象属性

针对结构体,KVC也可以直接操作,但是操作时候需要将结构体转成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来操作对象属性的属性

    LGStudent *student = [[LGStudent alloc] init];
    student.subject    = @"iOS";
    person.student     = student;
    [person setValue:@"大师班" forKeyPath:@"student.subject"];
    NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);

5.集合操作符

5.1 字典操作

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

- (void)dictionaryTest{
    
    NSDictionary* dict = @{
                           @"name":@"Cooci",
                           @"nick":@"KC",
                           @"subject":@"iOS",
                           @"age":@18,
                           @"length":@180
                           };
    LGStudent *p = [[LGStudent 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":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 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);
}

KVC 的底层实现

KVC-赋值过程

根据上面官方文档得知:

    1. 先依次查询有没有相关的方法:set< Key>:_set< Key>:setIs< Key>: 找到直接进行调用赋值。
    1. 若没有相关方法时,会查看类方法accessInstanceVariablesDirectly是否为YES时进入下一步。否则进入步骤4
    1. YES时,可以直接访问成员变量的来进行赋值,依次寻找变量 _< key >_is< Key>< key>is< Key>。找到则直接赋值,否则进入下一步。
  • 将会调用**setValue:forUndefinedKey:**方法进行抛出异常。可以自定义的实现为未找到的场景来避免抛出异常的行为。

KVC-取值过程

    1. 先依次寻找是否有相关成员变量:get< Key>< key>is< Key>,或者 _< key >, 有则进入步骤4,否则下一步。
    1. 查看类方法accessInstanceVariablesDirectly是否为YES,是则进入下一步,否则进入步骤5
    1. 依次寻找 _< key>_is< Key>< key>,或者 is< Key> 成员变量是否有值,有则进入步骤4,否则进入步骤5
    1. 如果检索到的属性值是对象指针,则只返回结果。如果值是支持的标量类型NSNumber,则将其存储在NSNumber实例中并 返回该值。如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回该对象。
    1. 将会调用 setValue:forUndefinedKey: 方法进行抛出异常。可以自定义的实现为未找到的场景来避免抛出异常的行为

总结

在开发中经常用用到kvc,需要注意的是,ios13以后,对部分私有属性,使用kvc的话,会崩溃,这点要注意。