美文网首页
第二十节—KVC(一)

第二十节—KVC(一)

作者: L_Ares | 来源:发表于2020-11-08 16:24 被阅读0次

    本文为L_Ares个人写作,以任何形式转载请表明原文出处。

    网上可以搜索到的KVC文章太多了,之所以都这么喜欢研究这个东西,是因为KVC的作用域之广泛,只要你还需要用到对象这个概念,怕是都有可能要碰到KVC帮你解决问题。

    本节将从苹果官方文档来进入KVC。不再使用源码是因为KVC的源码在Foundation框架中,找不到其开源的源码。

    kvc文档.png

    准备工作 : 苹果官方文档。必须要有这个。进入之后搜索栏自行搜索key value coding,不要搜索kvc这里直达KVC

    一、KVC基本简介

    1. KVC的定义

    • 英文全称 : key value coding(就是准备工作里面让大家搜的)

    • 中文全称 : 键值编码

    • 主要功能 :

      • OC的私有变量变成了单一名词 : 通过变量名称字符串直接访问成员变量,无论公有还是私有
      • 践行了OC的动态性 : 无需调用明确的存取方法动态的访问和修改对象属性。
    • 依赖性 : 根据KVC的主要功能就能知道,依赖的是Runtime

    2. KVC的简介

    下面这些都是从官方文档里面根据自身的一些了解翻译的,有不当之处,还请指出,感激不尽。

    (1). KVC本身是一种机制,它是根据NSKeyValueCoding非正式协议使用的。对象采用NSKeyValueCoding非正式协议可以对其属性进行访问。
    (2). 当一个对象兼容键值对编码(kvc)时,可以通过简洁、统一的消息传递接口利用字符串参数对它的属性进行访问。
    (3). KVC这种间接的访问机制,提供了一种直接访问实例变量和它们的settetgetter的方式。

    我们经常使用setter或者getter来访问对象的属性。大家也清楚setter可以给属性赋值,getter可以返回属性的值。在OC中,你还可以直接访问属性的底层的实例变量。

    使用上述的任何一种方式访问对象的属性都很简单,但是都需要调用属性自己的,特定的方法或者变量名。而且随着属性列表的增加或者更改,访问属性的代码也需要增加或者更改。KVC就提供了一种更简单的,更统一的消息传递接口,对所有遵循KVC机制的属性都可以适用。

    KVC是其他很多Cocoa技术的基础概念,比如说键值观察(KVO)Cocoa BindingCoreDataAppleScript(写mac脚本的)。在某些情况下,KVC还可以帮我们简化一些代码。

    到这里,大体对KVC也算有一个官方的理解了。这个模块的一些其他内容就不翻译了,后面有时间再翻译一下,也可以去网上搜索,有很多的小伙伴也写过的,就不赘述了,下面直接上第二个模块的内容,用代码来直接实现官方的一些规定。

    二、KVC中常见API

    1. KVC设置值

    1.1 key设置value
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    
    1.2 keyPath设置value
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    

    2. KVC取值

    2.1 通过key取value
    - (nullable id)valueForKey:(NSString *)key;
    
    2.2 通过keyPath取value
    - (nullable id)valueForKeyPath:(NSString *)keyPath; 
    

    3. 其他常见API

    • 对实例变量而言(不对属性),开启或者关闭实例变量赋值。默认返回YES,开启。
    + (BOOL)accessInstanceVariablesDirectly;
    

    大家都知道,实例变量和属性不同,是不默认生成setter方法的,如果不开启这个,又不自己给实例变量添加set方法的话,那么就不可以直接给实例变量进行赋值。

    • 检查给keyvalue是否是有效的。
      也就是说,可以验证给指定的keyset的值是否正确。如果不正确,可以替换或者拒绝,并且可以给error的地址上添加错误的原因。
    - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
    
    • 属性是NSMutableArray类型,通过key获取该属性。
    - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
    
    • 无法识别的key
      key不存在,且KVC找不到任何与key的字符串相关的字段或属性,会调用到这里,默认是抛出异常。
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    
    • 对无法识别的key赋值
      key不存在,KVC也找不到任何与key的字符串相关的字段或属性,无法对key进行设置value,会调用到这里,默认抛出异常。
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    
    • 设置keyvaluenil
      如果给一个已知的key设置value = nil;那么就会走到这里。
    - (void)setNilValueForKey:(NSString *)key;
    
    • 根据一组key找到value然后转成NSDictionary
      该方法可以用于模型转字典。
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    

    三、一些简单的KVC使用

    准备 : 创建一个Project--->App。创建一个继承于NSObjectJDPerson类。再创建一个继承于NSObjectJDMan类。

    JDPerson.h :

    #import <Foundation/Foundation.h>
    #import "JDMan.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef struct {
        float x,y,z;
    }ThreeFloats;
    
    @interface JDPerson : NSObject
    
    {
        @public
        NSString *myName;
    }
    
    @property (nonatomic, copy)   NSString         *name;
    
    @property (nonatomic, strong) NSArray          *array;
    
    @property (nonatomic, strong) NSMutableArray   *mutArr;
    
    @property (nonatomic, assign) int              age;
    
    @property (nonatomic)         ThreeFloats      threeFloats;
    
    @property (nonatomic, strong) JDMan            *man;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    

    JDMan.h :

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface JDMan : NSObject
    
    @property (nonatomic, copy) NSString         *name;
    
    @property (nonatomic, copy) NSString         *work;
    
    @property (nonatomic, copy) NSString         *hobby;
    
    @property (nonatomic, assign) int            age;
    
    @property (nonatomic, assign) int            height;
    
    @property (nonatomic, strong) NSMutableArray *bookArr;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    

    ViewController中引入JDPerson.hJDMan.h。准备工作完成。

    KVC中常见的使用

    1 基本类型设置值
    #pragma mark - KVC基本类型赋值与取值
    - (void)jd_kvc_basicType
    {
        /**
         普通的setter方法对对象进行赋值
         */
        JDPerson *person = [[JDPerson alloc]init];
        person.name    = @"JD";
        person.age     = 18;
        person->myName = @"LJD";
        NSLog(@"setter方法 : %@ - %d - %@",person.name,person.age,person->myName);
        
        /**
         1. KVC : 基本类型
         */
        [person setValue:@"JD_KVC" forKey:@"name"];
        [person setValue:@16 forKey:@"age"];
        [person setValue:@"LJD_JVC" forKey:@"myName"];
        NSLog(@"KVC : 基本类型 : %@ - %@ - %@",[person valueForKey:@"name"],
                                              [person valueForKey:@"age"],
                                              [person valueForKey:@"myName"]);
    }
    
    
    2 集合类型修改值
    #pragma mark - KVC集合类型修改值
    - (void)jd_kvc_collectionTypes
    {
        /**
         2. KVC : 集合类型
            修改集合中的第一个元素@"1"
            由于array是不可变数组,不可以直接进行修改
         */
        JDPerson *person = [[JDPerson alloc]init];
        person.array = @[@"1",@"2",@"3"];
        //普通KVC方式
        NSArray *array = [person valueForKey:@"array"];
        array = @[@"666",@"2",@"3"];
        [person setValue:array forKey:@"array"];
        NSLog(@"集合类型-普通KVC方式 : %@",[person valueForKey:@"array"]);
        //KVC API方式
        NSMutableArray *mutArr = [person mutableArrayValueForKey:@"array"];
        mutArr[0] = @"888";
        NSLog(@"集合类型-KVC API方式 : %@",[person valueForKey:@"array"]);
        
    }
    
    3 集合操作符

    这里包含一些KVC特殊的操作符,比如lowercaseString(小写)、@avg(平均数)、@count(数量)、@sum(求和)、@max(最大值)、@min(最小值)、@unionOfObjects(相同的key的value)、@distinctUnionOfObjects(相同的key的value并且去重)等等。

    直接上代码 :

    #pragma mark - KVC字典操作
    - (void)jd_kvc_dictionary
    {
        NSDictionary *dic = @{
            @"name"   : @"ljd",
            @"work"   : @"coder",
            @"age"    : @18,
            @"height" : @150,
            @"hobby"  : @"study"
        };
        
        JDMan *man = [[JDMan alloc] init];
        //普通的字典转模型
        [man setValuesForKeysWithDictionary:dic];
        NSLog(@"KVC字典操作-字典转模型 : %@-%@-%d-%d-%@",man.name,man.work,man.age,man.height,man.hobby);
        //通过key的数组转模型到字典
        NSArray *keyArray = @[@"name",@"work"];
        NSDictionary *dict = [man dictionaryWithValuesForKeys:keyArray];
        NSLog(@"KVC字典操作-通过key的数组转模型到字典 : %@",dict);
        
    }
    
    #pragma mark - KVC消息传递
    - (void)jd_kvc_messagePass
    {
        //消息从tempArray传递到了string
        NSArray *tempArray = @[@"Apple",@"Banana",@"Grapes",@"Peach"];
        NSArray *lengthArr = [tempArray valueForKeyPath:@"length"];
        NSLog(@"KVC消息传递-length : %@",lengthArr);
        NSArray *lowStrArr = [tempArray valueForKeyPath:@"lowercaseString"];
        NSLog(@"KVC消息传递-小写 : %@",lowStrArr);
    }
    
    #pragma mark - KVC聚合操作符
    - (void)jd_kvc_aggregation_operator
    {
        NSMutableArray *manArr = [NSMutableArray array];
        for (int i = 0; i < 6; i++) {
            JDMan *man = [JDMan new];
            NSDictionary *dic = @{
                @"name"  : @"JD",
                @"work"  : @"coder",
                @"hobby" : @"study",
                @"age"   : @(18 + i),
                @"height": @(175 + 2 * arc4random_uniform(6))
            };
            [man setValuesForKeysWithDictionary:dic];
            [manArr addObject:man];
        }
        //取对象中的`height`,结果是一个数组
        NSLog(@"KVC聚合操作符-height : %@",[manArr valueForKey:@"height"]);
        //取`height`平均数
        float avg_length = [[manArr valueForKeyPath:@"@avg.height"] floatValue];
        NSLog(@"KVC聚合操作符-平均数 : %f",avg_length);
        //`height`的数量
        int count = [[manArr valueForKeyPath:@"@count.height"] intValue];
        NSLog(@"KVC聚合操作符-属性数量 : %d",count);
        //求和
        int sum = [[manArr valueForKeyPath:@"@sum.height"] intValue];
        NSLog(@"KVC聚合操作符-求和 : %d",sum);
        //最大值
        int max = [[manArr valueForKeyPath:@"@max.height"] intValue];
        NSLog(@"KVC聚合操作符-最大值 : %d",max);
        //最小值
        int min = [[manArr valueForKeyPath:@"@min.height"] intValue];
        NSLog(@"KVC聚合操作符-最小值 : %d",min);
        
    }
    
    #pragma mark - KVC数组操作符
    - (void)jd_kvc_array_operator
    {
        NSMutableArray *manArr = [NSMutableArray array];
        for (int i = 0; i < 6; i++) {
            JDMan *man = [JDMan new];
            NSDictionary *dic = @{
                @"name"  : @"JD",
                @"work"  : @"coder",
                @"hobby" : @"study",
                @"age"   : @(18 + i),
                @"height": @(175 + 2 * arc4random_uniform(6))
            };
            [man setValuesForKeysWithDictionary:dic];
            [manArr addObject:man];
        }
        
        //取数组中的对象元素的某一属性,然后形成对象属性数组
        //通过key取
        NSLog(@"KVC数组操作符-key取属性 : %@",[manArr valueForKey:@"height"]);
        //通过keyPath取
        NSLog(@"KVC数组操作符-keyPath取属性 : %@",[manArr valueForKeyPath:@"@unionOfObjects.height"]);
        //通过keyPath取,并且属性去重
        NSLog(@"KVC数组操作符-keyPath取属性且去重 : %@",[manArr valueForKeyPath:@"@distinctUnionOfObjects.height"]);
        
    }
    
    #pragma mark - KVC嵌套数组操作符
    - (void)jd_kvc_nested_array_operator
    {
        NSMutableArray *manArr = [NSMutableArray array];
        for (int i = 0; i < 6; i++) {
            JDMan *man = [JDMan new];
            NSDictionary *dic = @{
                @"name"  : @"JD",
                @"work"  : @"coder",
                @"hobby" : @"study",
                @"age"   : @(18 + arc4random_uniform(6)),
                @"height": @(175 + i)
            };
            [man setValuesForKeysWithDictionary:dic];
            [manArr addObject:man];
        }
        
        NSMutableArray *personArr = [NSMutableArray array];
        for (int i = 0; i < 6; i++) {
            JDPerson *person = [JDPerson new];
            NSDictionary *dic = @{
                @"name"  : @"JD",
                @"age"   : @(18 + arc4random_uniform(6)),
            };
            [person setValuesForKeysWithDictionary:dic];
            [personArr addObject:person];
        }
        
        //嵌套数组
        NSArray *nestArr = @[manArr,personArr];
        
        //取嵌套数组的两个数组中对象元素的某一相同属性,然后形成对象属性数组
        NSLog(@"KVC嵌套数组操作符 - %@",[nestArr valueForKeyPath:@"@unionOfArrays.age"]);
        
        //去重
        NSLog(@"KVC嵌套数组操作符-去重 - %@",[nestArr valueForKeyPath:@"@distinctUnionOfArrays.age"]);
        
    }
    
    #pragma mark - KVC嵌套集合
    - (void)jd_kvc_nested_set
    {
        NSMutableSet *manSet = [NSMutableSet set];
        for (int i = 0; i < 6; i++) {
            JDMan *man = [JDMan new];
            NSDictionary *dic = @{
                @"name"  : @"JD",
                @"work"  : @"coder",
                @"hobby" : @"study",
                @"age"   : @(18 + arc4random_uniform(6)),
                @"height": @(175 + i)
            };
            [man setValuesForKeysWithDictionary:dic];
            [manSet addObject:man];
        }
        
        NSMutableSet *personSet = [NSMutableSet set];
        for (int i = 0; i < 6; i++) {
            JDPerson *person = [JDPerson new];
            NSDictionary *dic = @{
                @"name"  : @"JD",
                @"age"   : @(18 + arc4random_uniform(6)),
            };
            [person setValuesForKeysWithDictionary:dic];
            [personSet addObject:person];
        }
        
        //嵌套Set
        NSSet *nestSet = [NSSet setWithObjects:manSet, personSet, nil];
        NSLog(@"KVC嵌套集合-交集 - %@",[nestSet valueForKeyPath:@"@distinctUnionOfSets.age"]);
        
    }
    
    4 访问非对象属性

    非对象属性,就像那些常见的基本类型,还有结构体之类的。这里主要说一下结构体。因为有的人会利用这种方法做C/C++OC的混编。

    这种基本数据类型或者数据结构是不可以直接当value使用的,要根据其类型转换成NSValue或者其他的OC类。

    #pragma mark - KVC访问非对象属性
    - (void)jd_kvc_non_object_attribute
    {
        JDPerson *person = [[JDPerson alloc] init];
        ThreeFloats tfValue = {1.f,2.f,3.f};
        NSValue *value = [NSValue valueWithBytes:&tfValue objCType:@encode(ThreeFloats)];
        [person setValue:value forKey:@"threeFloats"];
        
        NSValue *resultValue = [person valueForKey:@"threeFloats"];
        
        NSLog(@"KVC访问非对象属性-结构体 - %@",resultValue);
        
        ThreeFloats three_value;
        [resultValue getValue:&three_value];
        NSLog(@"KVC访问非对象属性-打印 - %f-%f-%f",three_value.x,three_value.y,three_value.z);
        
        
    }
    
    5 层层访问或者说对象嵌套

    就是说对象的属性还是个对象。通过keyPath就可以了。

    #pragma mark - KVC 对象中的对象
    - (void)jd_kvc_obj_include_obj
    {
        JDPerson *person = [[JDPerson alloc] init];
        JDMan *man = [[JDMan alloc] init];
        man.name = @"LJD";
        person.man = man;
        NSLog(@"KVC 对象中的对象-未改变 - %@",person.man.name);
        //利用kvc的keyPath再给person的man中的name赋值
        [person setValue:@"JD" forKeyPath:@"man.name"];
        NSLog(@"KVC 对象中的对象-改变后 - %@---%@",[person valueForKeyPath:@"man.name"],man.name);
    }
    

    相关文章

      网友评论

          本文标题:第二十节—KVC(一)

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