zoukankan      html  css  js  c++  java
  • Runtime-KVC

    KVC

    ​ KVC又叫做键值编码,不借助类提供的接口(setter和getter),访问类中的属性和成员变量,即使是私有属性和成员变量,也可以通过KVC进行读写,大大提高编程灵活性,但也破坏了系统封装性。

    ​ KVC是借助runtime,来获取类中信息,从而进行数据的读写。NSObject类的一个分类中定义了KVC的功能,因此只有继承NSObject的类才可以使用KVC。

    KVC使用方式

    ​ KVC可以访问类中的基本数据类型、结构体、非集合对象和集合对象。类的结构并不是只有一层,对于属性或者成员变量也是一个类的多层类,KVC可以实现读写。

    forKey

    ​ 使用forKey只能访问类中单层属性。

    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    - (nullable id)valueForKey:(NSString *)key;
    

    forKeyPath

    ​ 使用forKey可以访问类中深层属性,即只有类的属性或者成员变量是一个类,就可以通过KVC访问这个类中的属性。

    - (nullable id)valueForKeyPath:(NSString *)keyPath;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    

    基本类型和结构体的KVC

    ​ 由于KVC是基于NSObject的,因此使用KVC读写基本类型或者结构体时,需要将其转换成NSNumber或者NSValue。

    ZPPerson *p = [ZPPerosn new];
    // 对于基本类型读写,需要转换成NSNumber。
    [p servalue:@(12) forKey:@"age"];
    NSNumber *num = [p valueForKey:@"age"];
    
    //  对于结构体类型读写,需要转换成NSValue。
    typedef struct {
        int chinese;
        int math;
        int english;
    } GradeStruct; // 成绩
    
    GradeStruct gds = {92, 100, 34};
    NSValue *valueOfGradeStruct = [NSValue value:&gds withObjCType:@encode(GradeStruct)];
    [p setValue:valueOfGradeStruct forKey:@"gds"];
    GradeStruct gds2;
    [[p valueForKey:@"gds"] getValue:&gds2];
    

    集合操作

    数组

    ​ 对于数组的KVC操作有两种:

    1. 数组的聚集函数,sum、avg、min、max计算元素属性的总和、平均值、最小、最大
    2. 属性为数组,元素为自定义对象,读取数组元素中的某个属性。
    
    @interface ZPPerson5 : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, strong) NSMutableArray<ZPPerson6 *> *friends;
    @end
    
    NSLog(@"%@",[p valueForKeyPath:@"friends.@sum.age"]);
    NSLog(@"%@",[p valueForKeyPath:@"friends.@avg.age"]);
    NSLog(@"%@",[p valueForKeyPath:@"friends.@min.age"]);
    NSLog(@"%@",[p valueForKeyPath:@"friends.@max.age"]);
    NSArray *arr = [p mutableArrayValueForKeyPath:@"friends.age"];
    

    字典

    ​ KVC提供了批量的读写数据的方式,字典与模型的相互转换,支持属性和成员变量,支持基本类型、结构体、OC类型。

    // ZPPerson5.h
    @interface ZPPerson5 : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, strong) ZPPerson5 *father;
    @property (nonatomic, strong) NSMutableArray *friends;
    @end
    
    // ZPPerson5.m
    @interface ZPPerson5 ()
    {
        BOOL _sex;
        NSString * test1;
        NSString * _test2;
        NSString * isTest3;
        NSString * _isTest4;
        NSMutableArray *_arrms;
        NSSet *_sets;
        
        GradeStruct _gds ;
        
    }
    @end
    
    // KVCy提供了两个关于字典的方法,实现字典与模型的转换。
    ZPPerson6 * p = [ZPPerson new];
    // 模型转字典
    NSDictionary *dictOfPerson = [p dictionaryWithValuesForKeys:@[@"age", @"name", @"father", @"test1", @"_test2", @"isTest3", @"_isTest4"]];
    
    // 字典转模型
    NSDictionary *dict = @{@"name":@"王司徒", @"age":@(76), @"test1":@"ggggg", @"_test2":@"jjjjj", @"isTest3":@"kkkkk", @"_isTest4":@"vvvvv"};
    [p setValuesForKeysWithDictionary:dict];
    

    KVC搜索key的步骤

    setValue:forKey:和setValue:forKeyPath:

    1、查询是否有set:方法,有则调用,KVC结束。
    2、系统调用该类中实现的accessInstanceVariablesDirectly方法,是否还要继续搜索,YES则继续向下走,NO则直接跳到第4步。
    3、按照_key、_isKey、key、isKey顺序查找是否相关的成员变量,找到则KVC结束,没有找到进入第4步。
    4、上面步骤走完没有找到,调用setValue:forUndefinedKey:方法,如果不实现程序崩溃。

    valueForKey:和valueForKeyPath:

    1、查询是否有get,,is方法,有则调用。
    2、查询是否有countOf、objectInAtIndex、AtIndexes方法,如果countOf方法和另外两个方法中的一个被找到,那么就会返回一个可以响应NSArray所有方法的代理集合(它是NSKeyValueArray。
    3、查找countOf,enumeratorOf,memberOf格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合。
    4、系统调用+accessInstanceVariablesDirectly方法,是否还要继续搜索,返回NO,则直接进入第6步。
    5、按照_,_is,,is的顺序搜索成员变量名。
    6、调用valueForUndefinedKey:方法,如果没有实现则系统崩溃。

    总结:

    1、从上面的步骤可以看出,KVC首先搜索setter和getter,因此即使没有定义成员变量只是实现了setter和getter方法,可以用使用KVC。

    2、KVC只会搜索相应名称的setter、getter或者成员变量,因此像分类中定义属性时,property_list有key对应属性,而ivar_list没有对应成员变量,又没有实现相应setter和getter方法,使用KVC会导致找不到key而程序崩溃。

    KeyPath的搜索步骤

    // ZPPerson.h
    @interface ZPPerson5 : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, strong) ZPPerson5 *father;
    @property (nonatomic, strong) NSMutableArray *friends;
    @end
    
    // ZPPerson5.m
    @interface ZPPerson5 ()
    {
        ZPIDCard * _card;
    }
    @end
    @implementation ZPPerson5
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _card = [ZPIDCard new];
            return self;
        }
        return nil;
    }
    // 当使用KVC查找到一定程度时,系统会调用该方法,询问是否还需要向下查找。
    	+ (BOOL)accessInstanceVariablesDirectly
    	{
        NSLog(@"ZPPerson5,查找到一定程度,是否还要继续搜索");
        return NO;
    	}
    @end
     
    // ZPCard
    @interface ZPIDCard : NSObject
    
    @end
      
    @interface ZPIDCard()
    {
        NSString * _idCard;
    }
    @end
    
    @implementation ZPIDCard
    
    +(BOOL)accessInstanceVariablesDirectly{
      NSLog(@"ZPIDCard,查找到一定程度,是否还要继续搜索");
        return NO;
    }
    @end
      
      
    // main
    ZPPerson5 *p = [ZPPerson5 new];
    [p setValue:@"asdfsfd" forKeyPath:@"idCard._idCard"];
    

    分别控制ZPPerson5和ZPIDCard类中的+accessInstanceVariablesDirectly方法。

    1、ZPPerson5返回NO,ZPIDCard无所谓,终端打印ZPPerson5类中acess..方法,系统崩溃。

    2、ZPPerson5返回YES,ZPIDCard返回NO,终端打印ZPIDCard类中acess..方法,系统崩溃。

    由此可以得出,KeyPath其实还是与key的搜索步骤一样,例如"idCard._idCard",查找ZPPerson5类中是否有“idCard”对应的setter或者成员变量,如果找到进入该成员变量的类中(ZPIDCard),查找ZPIDCard中"__idcard"对应的setter或者成员变量。

    KVC异常处理

    KVC中的两种异常:

    1、给基本数据类型或者结构体设置nil。

    2、找不到key。

    设置nil

    ​ iOS允许使用KVC给OC对象设置nil,但不允许使用KVC给值类型设置nil。

    ​ nil是给OC对象用来初始化的,不能赋值给值类型,因此如果使用KVC操作的成员变量如果是值类型,设置nil时,系统崩溃。

    ​ 当KVC给值类型设置nil时:

    > 1、查看该类是否实现setNilValueForKey:方法,实现则调用并结束KVC,否则进行第2步。
    >
    > 2、程序崩溃。
    

    找不到key

    当KVC找不到对应的key时:

    1、查看该类是否实现了setValue:forUndefinedKey:或者valueForUndefinedKey:,实现了则调用该方法,并结束KVC,否则执行第2步。

    2、程序崩溃。

    使用keyPath时,和使用key一样,字符串中哪个节点触发了着两种异常,则查看该节点对应的类是否实现了相关方法。例如上面的"idCard._idCard",如果查找不到"__idCard",则查看ZPIDCard类是否实现相关方法。

    KVC的键值验证

    ​ 该功能其实是,用来验证key对应的value是否符合要求,需要在使用KVC的类中实现相应的验证方法。

    @interface ZPPerson5 : NSObject
    @property (nonatomic, assign) NSInteger age;
    @end
    
    @implementation ZPPerson5
    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError * _Nullable __autoreleasing *)outError
    {
        NSNumber *age = *ioValue;
        if (age.integerValue < 10) {
            return NO;
        }
        
        return YES;
    }
    @end
    
    // main
    ZPPerson5 *p = [ZPPerson5 new];
    NSNumber *age = @(5);
    NSError * __autoreleasing error = nil; // 指向指针的指针需要是否__autoreleasing修饰
    BOOL s = [p validateValue:&age forKeyPath:@"age" error:&error];
    NSLog(@"");
    
  • 相关阅读:
    xmlTextTextReaderNodeType来读取XML元素的类型
    [转]关于两个坐标点的距离的计算问题
    Incorrect decrement of the reference count of an object that is not owned at this point by the caller1
    让ipad时时显示内存剩余量
    【转】苹果开发者账号注册流程
    自定义百度地图气泡
    [转] iOS 常用数学函数
    [转]JDK环境变量的配置
    纬度在换算距离上一度等于多少公里?
    [转]UIDevice uniqueGlobalDeviceIdentifier(百度地图API的那些事)
  • 原文地址:https://www.cnblogs.com/Zp3sss/p/13296483.html
Copyright © 2011-2022 走看看