• 【iOS-KVC学习】


    KVC

    KVC是什么

    • KVC是Key-Value-Coding的缩写,通过查询,俗称键值编码。也可以说在iOS开发的过程KVC提供的机制允许我们通过key值访问对象的属性或成员变量,
    • 和KVO一样KVC也是针对NSOBject子类的一种方法,其中在NSKeyValueCoding中提供了KVC通用的访问方法,分别是getter方法valueForKey和setter方法setValue:forKey,以及其衍生的keyPath方法,这两个方法是各个类通用的。并且由KVC提供默认的实现,我们也可以自己重写对应的方法来改变实现。

    KVC的基础方法API

    • KVC的定义都是对NSObject的扩展来实现的,KVC较为重要的四个方法如下
    - (nullable id)valueForKey:(NSString *)key;      //直接通过Key来取值
    
    - (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
    
    - (nullable id)valueForKeyPath:(NSString *)keyPath;   //通过KeyPath来取值
    
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
    
    • 四个方法可以分为俩类总结
    • 设值
      • - (void)setValue:(nullable id)value forKey:(NSString *)key;
      • -(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    • 取值:
      • (nullable id)valueForKey:(NSString *)key;
      • - (nullable id)valueForKeyPath:(NSString *)keyPath;

    KVC设值

    key方法
    • 在使用的时候我们将属性名写在Key方法,Value处写我们的属性赋值
    Person* person = [[Person alloc]init];
     [person setValue:@101 forKey:@"pAge"];
    
    keyPath方法
    • 多级访问-KeyPath路径方法,KVC进行多级访问时,类似于属性调用一样用点语法进行访问即可
      请添加图片描述
    • 设置两个继承于 NSObject的类- Person Dog,在Person里设置一个Dog属性,Dog类有一个age属性,我们通过KVC的keyPath路径方法来设置Dog类的age;
    • person
    #import <Foundation/Foundation.h>
    #import "Dog.h"
    NS_ASSUME_NONNULL_BEGIN
    /***
     KVC - 成员变量
     
     */
    @interface Person : NSObject {
    }
    /**
     添加Dog属性
     */
    @property (nonatomic, strong)Dog* dog;
    @end
    
    • Dog
    @interface Dog : NSObject
    @property (nonatomic, assign)int age;
    @end
    
    • 在ViewController里打印试试
     Person* person = [[Person alloc]init];
     [person setValue:@120 forKeyPath:@"dog.age"];
        /***
         KVC- 嵌套适用,可以用KeyPath的点语法
         */
         //这里牵扯到了获取值方法,也是多级访问
     id age2 = [person valueForKeyPath:@"dog.age"];![请添加图片描述](https://img-blog.csdnimg.cn/5fc133845dd14bfda5db043b0e3b3e35.png)
    
     NSLog(@"keyPath获得的年龄是%@", age2);
    

    请添加图片描述

    KVC设值的异常

    • 在设值的过程里一旦不小心把值置为nil,我们需要重写setNilValueForKey: 即可

    KVC取值

    • 和上面设值一样,取值也是2个方法valueForKey,valueForKeyPath,这个和设置一样,多重设值就是keyPath的点语法,这里写一个demo探究一下取值过程的先后顺序,经过测试,取值过程有2个优先级
    • 这里仅用一个Person类来探究取值过程的先后顺序
    • 剽窃的图
      在这里插入图片描述
    第二优先级
    • Person类里设置一个name的成员变量,而编译器在寻找value的时候存在四个不同的命名,_key, _isKey, key, isKey
    #import <Foundation/Foundation.h>
    #import "Dog.h"
    NS_ASSUME_NONNULL_BEGIN
    /***
     KVC - 成员变量
     
     */
    @interface Person : NSObject {
        NSString* _name;
        NSString* _isName;
        NSString* name;
        NSString* isName;
    }
    
    • 在调用valueForKey方法的时候,我们重写init方法
    #import "Person.h"
    #import "Dog.h"
    @implementation Person
    - (instancetype)init {
        if (self = [super init]) {
            //成员变量
            _name = @"_name";
            _isName = @"_isName";
            name = @"name";
            isName = @"isName";
        }
        return self;
    }
    
    
    • 打印
     Person* person = [[Person alloc]init];
        NSString* name = [person valueForKey:@"name"];
        NSLog(@"%@", name);
    

    请添加图片描述

    • 当这四种成员变量都存在的时候编译器先去寻找_key变量,没有_key呢?
      请添加图片描述
    • 经过测试,在进行编译的时候按照_key,_isKey,key,iskey的顺序查找,
    + (BOOL)accessInstanceVariablesDirectly
    • 其实编译器能否取查找_key,_isKey,key,iskey在这之前取决于一个函数
    • +(BOOL)accessInstanceVariablesDirectly,
      默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索,如果返回NO系统会抛出异常
    • 测试,在没有Setter方法的前提下
    + (BOOL)accessInstanceVariablesDirectly {
        return NO; 
    }
    

    请添加图片描述

    • 抛出异常,我们需要重写图中的- (id)valueForUndefinedKey:(NSString *)key
      返回nil即可解决异常
    - (id)valueForUndefinedKey:(NSString *)key {
        return nil;
    }
    
    • 测试请添加图片描述
    第一优先级Setter方
    • 前面提到的都是系统在么有找到Setter方法的前提在+ (BOOL)accessInstanceVariablesDirectly 返回YES去寻找key,但最高的优先级是setter方法,编译器在上面的函数返回YES之前寻找setter方法
    • person
    • 把init和setter方法全部打开,然后设置打印不同的名字,看出现什么,即可代表了优先级的顺序
    #import <Foundation/Foundation.h>
    #import "Dog.h"
    NS_ASSUME_NONNULL_BEGIN
    /***
     KVC - 成员变量
     
     */
    @interface Person : NSObject {
        NSString* _name;
        NSString* _isName;
        NSString* name;
        NSString* isName;
        int pAge;
    }
    
    
    #import "Person.h"
    #import "Dog.h"
    @implementation Person
    - (instancetype)init {
        if (self = [super init]) {
            //成员变量
            _name = @"_name";
            _isName = @"_isName";
            name = @"name";
            isName = @"isName";
        }
        return self;
    }
    
    /***
     第一优先级,3个Setter方法,getKey > key >isKey
     */
    - (NSString*)name {
        return @"Hank";
    }
    - (NSString*)getName {
        return @"getHank";
    }
    - (NSString*)isName {
        return @"isHank";
    }
    

    请添加图片描述

    • 通过代码可知在setter方法都存在的时候也是有优先级的,getKey > key > isKey
    • 如果setter方法都不存在那么系统会调用(BOOL)accessInstanceVariablesDirectly 的返回值按照之前的顺序查找value

    第一优先级的有序集合类方法

    尝试返回一个Int
    • 把getName里返回成int 试试发现
    - (int)getName {
        /***
         发现第二优先级方法的来源
         name被包装成了NSDCFNumeber类型
         */
        return 10;
    }
    
    • 打断点发现,当我们把返回值返回一个int类的时候系统会把 name被包装成了NSDCFNumeber类型,如此引出了第一优先级之后的两个方法
    • -(NSInteger)countOfName
    • -(id)objectInNameAtIndex :(NSInteger)index
      请添加图片描述
    • 发现来源- 在3个getter方法里返回一个 int 发现系统包装成了
      第二优先级,当3个getter方法不存在的时候,系统调用这个方法,生成一个NSKeyValueArray数组!
    • 测试 Person.m,把第一优先级的方法注释,对比init和上面的方法
    #import "Person.h"
    #import "Dog.h"
    @implementation Person
    - (instancetype)init {
        if (self = [super init]) {
                //成员变量
            _name = @"_name";
            _isName = @"_isName";
            name = @"name";
            isName = @"isName";
        }
        return self;
    }
    
    /***
     第一优先级,3个Setter方法,getKey > key >isKey
     */
    //- (NSString*)name {
    //    return @"Hank";
    //}
    //- (NSString*)getName {
    //    return @"getHank";
    //}
    - (int)getName {
        /***
         发现第二优先级方法的来源
         name被包装成了NSDCFNumeber类型
         */
        return 10;
    }
    //- (NSString*)isName {
    //    return @"isHank";
    //}
    
    /***
     发现来源- 在3个getter方法里返回一个 int 发现系统包装成了
     第二优先级,当3个getter方法不存在的时候,系统调用这个方法,生成一个NSKeyValueArray数组!新发现
     */
    - (NSInteger)countOfName {
        return 12;
    }
    - (id)objectInNameAtIndex :(NSInteger)index{
        return @"objectName";
    }
    
    

    请添加图片描述

    • 可以看出俩个函数,一个是返回长度,一个是返回内容的,当setter方法不存在的时候会先调用这个函数,(了解即)

    取值优先级的总结

    • 第一优先级,先找相关方法-,getKey > key > isKey
    • 上述方法不存在,找- -(NSInteger)countOfName And
      -(id)objectInNameAtIndex :(NSInteger)index
      -当上述第一优先级都不存在的时候,系统查找(BOOL)accessInstanceVariablesDirectly返回值,YES则按_key,_isKey,key,iskey顺序寻找变量取值,NO则会抛出异常,需要重写- (id)valueForUndefinedKey:(NSString *)key 方法

    KVC批量动态取值方法(2种)

    • KVC还可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value
      -NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
    • 也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给User对象的属性赋值。
      - (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
    • 新建newPerson
    @interface newPerson : NSObject
    @property (nonatomic, assign) NSInteger pAge;
    @property (nonatomic, copy) NSString* pName;
    @property (nonatomic, copy) NSString* pSex;
    @end
    
    • ViewController
    在这里插入代码片#import "Person.h"
    #import "ViewController.h"
    #import "newPerson.h"
    /**
     
     
     
     
     
     */
    @interface ViewController () 
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
     
        /**
         KVC批量取值测试
         */
        NSDictionary* pDictionary = @{@"pName":@"Lyt", @"pAge":@"19", @"pSex":@"Girl"};
        newPerson* Nperson = [[newPerson alloc] init];
        [Nperson setValuesForKeysWithDictionary:pDictionary];
        NSLog(@"newPerson.pName: %@", Nperson.pName);
        NSLog(@"newPerson.pAge: %ld", Nperson.pAge);
        NSLog(@"newPerson.pSex: %@", Nperson.pSex);
        
        NSDictionary* returnDictionary = [Nperson dictionaryWithValuesForKeys:@[@"pName", @"pAge", @"pSex"]];
        NSLog(@"returnDictionary%@", returnDictionary);
    }
    @end
    
    

    请添加图片描述

    类的属性和字典不匹配

    • 对于传入的字典里KVC还可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value的方法,如果字典里出现了类没有的属性,系统会崩溃,我把pSex改成了Sex
    NSDictionary* pDictionary = @{@"pName":@"Lyt", @"pAge":@"19", @"Sex":@"Girl"};
        newPerson* Nperson = [[newPerson alloc] init];
        [Nperson setValuesForKeysWithDictionary:pDictionary];
        NSLog(@"newPerson.pName: %@", Nperson.pName);
        NSLog(@"newPerson.pAge: %ld", Nperson.pAge);
        NSLog(@"newPerson.pSex: %@", Nperson.pSex);
    

    请添加图片描述

    重写-(void)setValue:(id)value forUndefinedKey:(NSString *)key
    • 如果类和字典不匹配(字典的传入数量大于类),在不匹配的类的M文件里面重写-(void)setValue:(id)value forUndefinedKey:(NSString *)key方法即可
    #import "newPerson.h"
    
    @implementation newPerson
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        if ([key isEqualToString:@"Sex"]) {
            self.pSex = (NSString*) value;
        }
    }
    @end
    

    总结

    • KVO和KVC还有联系,日后会更新KVO和KVC的联系
  • 相关阅读:
    JAVA中PRIORITYQUEUE详解
    Nginx 背锅解析漏洞
    SSM整合流程
    Spring注解驱动之BeanPostProcessor后置处理器详解
    两天学会微服务网关Gateway-Gateway工作原理
    8位ADC是256还是255?
    一道session文件包含题
    Jenkins怎么发邮件,5年字节自动化大神手把手教你
    Linux防火墙常用操作及端口开放
    全屋灯具选购指南,如何选择合适的灯具。福州中宅装饰,福州装修
  • 原文地址:https://blog.csdn.net/weixin_61639290/article/details/126950497