iOS底层之KVC

在讲KVC之前我们先来了解几个概念

iOS 成员变量,实例变量,属性的区别

  • 成员变量
    一个类里面所有的变量都是成员变量
  • 实例变量
    实例创建出来的变量,比如Class
    id 不一定,id是特殊的Class
  • 属性
    属性一般会有一个默认的setter+getter方法
    我们知道苹果早期的编译器是GCC,后来变成了LLVM
    经过LLVM,如果发现没有匹配到实例变量的属性的时候,就会自动创建一个_
@interface Person : NSObject{
    @public
    NSString *myName; //成员
    id hell; // id - > class
    int age;
}
@property (nonatomic, copy) NSString *name;

KVC取值赋值原理

为什么要介绍上面三个东西,因为KVC就是在上面这三个基础上操作的
KVC的源码并没有开源,所以我们学习KVC的方法就是去查看苹果的官方文档
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/SearchImplementation.html#//apple_ref/doc/uid/20000955-CJBBBFFA

通过官方文档我们可以看出:
KVC是通过间接访问成员变量的一种机制
通过键值编码
这里我简单翻译了一下关键的部分

Getter的搜索方式

  1. 搜索实例与像一个名字找到的第一个访问方法getis,或者_,按照这个顺序。如果找到,则调用它并执行步骤5。否则,继续下一步。

  2. 如果未找到简单的访问器方法,请在实例中搜索名称与模式countOfobjectInAtIndex:(与NSArray该类定义的原始方法AtIndexes:相对应)和(与该[NSArray](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSArrayClassCluster/Description.html#//apple_ref/occ/cl/NSArray)方法相对应)相匹配的方法objectsAtIndexes:

    如果找到其中的第一个以及其他两个中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并将其返回。否则,请继续执行步骤3。

    代理对象随后将任何NSArray接收到的一些组合的消息countOfobjectInAtIndex:AtIndexes:消息给键-值编码创建它兼容的对象。如果原始对象还实现了名称为的可选方法get:range:,则代理对象也会在适当时使用该方法。实际上,代理对象与与键值编码兼容的对象一起使用,可以使基础属性的行为就好像它是NSArray,即使不是。

  3. 如果没有找到简单的访问方法或阵列访问方法组,寻找一个三重的方法命名countOfenumeratorOfmemberOf:(对应于由所定义的原始的方法[NSSet](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSSetClassCluster/Description.html#//apple_ref/occ/cl/NSSet)类)。

    如果找到所有三个方法,请创建一个响应所有NSSet方法的集合代理对象并将其返回。否则,请继续执行步骤4。

    随后,此代理对象将NSSet收到的任何消息转换为,和消息的某种组合countOf,以创建该对象的对象。实际上,代理对象与与键值编码兼容的对象一起使用,可以使基础属性的行为就好像它是,即使不是。 enumeratorOf``memberOf:``NSSet

  4. 如果发现收集的访问方法没有简单的存取方法或者组,如果接收器的类方法[accessInstanceVariablesDirectly]返回YES,搜索名为实例变量__is,或者is,按照这个顺序。如果找到,请直接获取实例变量的值,然后继续执行步骤5。否则,请继续执行步骤6。

  5. 如果检索到的属性值是对象指针,则只需返回结果。

    如果该值是所支持的标量类型NSNumber,则将其存储在NSNumber实例中并返回它。

    如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象。

  6. 如果其他所有方法均失败,请调用[valueForUndefinedKey:]。默认情况下会引发异常,但是的子类NSObject可能提供特定于键的行为。

Setter

setValue:forKey: 使用以下步骤

  1. 按此顺序查找第一个名为set:or_set的访问器。如果找到,请使用输入值(或根据需要解包的值)调用它并完成。

  2. 如果没有实现上面两个set:or_set,如果类方法accessInstanceVariablesDirectly返回YES(默认),寻找一个实例变量与名称类似__is,或者is,按照这个顺序。如果找到,直接用输入值(或展开值)设置变量并完成操作。

  3. 在找不到访问器或实例变量时,调用setValue:forUndefinedKey:。默认情况下会引发异常,但是的子类NSObject可能提供特定于键的行为。

如果你不了解这个流程,你就会很迷惑。

流程图:

image.png

KVC的延伸应用

KVC还有一些比较有意思的东西

#pragma mark - array取值
- (void)arrayDemo{
    
    NSLog(@"******************array取值****************************");

    LGPerson *p = [LGPerson new];
    p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
    NSArray *arr = [p valueForKey:@"pens"];
    NSLog(@"pens = %@", arr);
    //NSLog(@"%@",arr[0]);
    NSLog(@"%d",[arr containsObject:@"pen1"]);
    // 遍历
    NSEnumerator *enumerator = [arr objectEnumerator];
    NSString *str = nil;
    while (str = [enumerator nextObject]) {
        NSLog(@"%@", str);
    }
}

#pragma mark - 字典操作

- (void)dictionaryTest{
    
    NSLog(@"******************KVC字典转模型****************************");
    
    NSDictionary* dict = @{
                           @"name":@"Fuye",
                           @"nick":@"KC",
                           @"subject":@"iOS",
                           @"age":@18,
                           @"length":@180
                           };
    LGPerson *p = [[LGPerson alloc] init];
    // 字典转模型
    [p setValuesForKeysWithDictionary:dict];
    NSLog(@"%@",p);
    // 键数组转模型到字典
    NSArray *array = @[@"name",@"age"];
    NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
    NSLog(@"%@",dic);
}

#pragma mark - KVC消息传递
- (void)arrayMessagePass{
    NSLog(@"******************KVC消息传递****************************");

    NSArray *array = @[@"Rose",@"Fuye",@"Natasy",@"ChangE"];
    NSArray *lenStr= [array valueForKeyPath:@"length"];
    NSLog(@"%@",lenStr);// 消息从array传递给了string
    NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
    NSLog(@"%@",lowStr);
}

#pragma mark - 聚合操作符
// @avg、@count、@max、@min、@sum
- (void)aggregationOperator{
    
    NSLog(@"******************@avg、@count、@max、@min、@sum****************************");

    NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        LGPerson *p = [LGPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(180 + 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);
}

// 数组操作符 @distinctUnionOfObjects @unionOfObjects
- (void)arrayOperator{
    NSLog(@"****************** @distinctUnionOfObjects @unionOfObjects****************************");
    NSMutableArray *personArray = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        LGPerson *p = [LGPerson 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"]);
    // 返回操作对象指定属性的集合
    NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.length"];
    NSLog(@"arr1 = %@", arr1);
    // 返回操作对象指定属性的集合 -- 去重
    NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
    NSLog(@"arr2 = %@", arr2);
    
}

// 嵌套集合(array&set)操作 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
- (void)arrayNesting{
    
    NSLog(@"******************@distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets****************************");
    
    NSMutableArray *personArray1 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        LGPerson *person = [LGPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 2*arc4random_uniform(6)),
                               };
        [person setValuesForKeysWithDictionary:dict];
        [personArray1 addObject:person];
    }
    
    NSMutableArray *personArray2 = [NSMutableArray array];
    for (int i = 0; i < 6; i++) {
        LGPerson *person = [LGPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 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);
}

- (void)setNesting{
    
    NSLog(@"******************setNesting****************************");

    NSMutableSet *personSet1 = [NSMutableSet set];
    for (int i = 0; i < 6; i++) {
        LGPerson *person = [LGPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 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++) {
        LGPerson *person = [LGPerson new];
        NSDictionary* dict = @{
                               @"name":@"Tom",
                               @"age":@(18+i),
                               @"nick":@"Cat",
                               @"length":@(175 + 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);
}

如果不阅读官方文档,很难想象KVC还有这么有意思的东西可以玩一玩,可以通过KVC做很多事,所以对于底层的探索是每一个开发者都应该去做的。

你可能感兴趣的:(iOS底层之KVC)