iOS 关于KVC的一些总结

作者: 拧发条鸟xds | 来源:发表于2018-08-29 20:35 被阅读0次

    本文参考:

    KVC官方文档

    KVC原理剖析

    iOS KVC详解


    KVC 简介

    • KVC全称是Key Value Coding(键值编码),是一个基于NSKeyValueCoding非正式协议实现的机制,它可以直接通过key值对对象的属性进行存取操作,而不需通过调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性,而不是在编译时确定

    • KVC提供了一种间接访问属性方法或成员变量的机制,可以通过字符串来访问对象的的属性方法或成员变量。

    • 在实现了访问器方法的类中,使用点语法和KVC访问对象其实差别不大,二者可以任意混用(因为KVC会首先搜索访问器方法,见下文)。但是没有访问器方法的类中,点语法无法使用,这时KVC就有优势了。

    • KVC和KVO都是基于OC的动态特性和Runtime机制的

    KVC 通用的访问方法

    1. 通用的访问方法
    • getter方法:
    valueForKey:
    
    • setter方法:
    setValue:forKey:
    
    1. 衍生的keyPath方法,用来进行深层访问(key使用点语法),也可单层访问:
    • keyPath的setter方法:
    setValue: forKeyPath:
    
    • keyPath的getter方法:
    valueForKeyPath:
    

    示例:

    Address.h:

    #import <Foundation/Foundation.h>
     
    @interface Address : NSObject
    @property (copy, nonatomic) NSString *city;
    @property (copy, nonatomic) NSString *street;
    @end
    

    Person.h:

    #import <Foundation/Foundation.h>
    #import "Address.h"
     
    @interface Person : NSObject
     
    @property (copy, nonatomic) NSString *name;
    @property (assign, nonatomic) NSInteger *sex;
    @property (strong, nonatomic) NSNumber *age;
    @property (strong, nonatomic) Address *address;
     
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
       
        Person *myself = [[Person alloc] init];
     
        [myself setValue:@"xds" forKey:@"name"];
       
        NSLog(@"-------name = %@",myself.name);
        NSLog(@"-------name = %@",[myself valueForKey:@"name"]);
       
        /**
         keyPath的setter方法:setValue: forKeyPath:
         keyPath的getter方法:valueForKeyPath:
        
         keyPath为多级访问,使用点语法
         */
       
        //注意,这里要想使用keypath对adress的属性进行赋值,必须先给myself赋一个Address对象
        Address *myAddress = [[Address alloc] init];
       
        [myself setValue:myAddress forKey:@"address"];
       
        //KeyPath为多级访问
        [myself setValue:@"rizhao" forKeyPath:@"address.city"];
     
        NSLog(@"-------city = %@",myself.address.city);
     
        NSLog(@"-------city = %@",[myself valueForKeyPath:@"address.city"]);
       
    }
     
    

    keypath

    除了对当前对象的属性进行赋值外,还可以对其更“深层”的对象进行访问

    keypath可以访问到array数组中所有存储的对象的属性,前提是对象类型是一样的

    例如下面例子中,通过valueForKeyPath:将数组中所有对象的name属性值取出,并放入一个数组中返回

    NSArray *names = [array valueForKeyPath:@"name"];
    

    KVC 的多值操作

    批量取值操作

    KVC还有更强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回。

    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    

    批量赋值操作

    同样,也可以通过KVC进行批量赋值。使用对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给User对象的属性赋值。

    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    

    示例

    示例如下:

    //批量赋值
    NSDictionary *dic = @{
                          @"name":@"xiaoMing",
                          @"sex":@1,
                          @"age":@12,
                          @"address":myAddress
                          };
     
    [myself setValuesForKeysWithDictionary:dic];
     
    //批量取值
    NSArray *keys = @[@"name",@"age",@"sex",@"address"];
     
    NSDictionary *values = [myself dictionaryWithValuesForKeys:keys];
     
    NSLog(@"%@",values);
    

    输出:

    2018-08-25 15:45:57.316650+0800 KVC[1145:231458] {
        address = "<Address: 0x60400003cc40>";
        age = "<null>";
        name = xds;
        sex = 0;
    }
    

    使用 KVC 进行字典转模型

    可以使用 setValuesForKeysWithDictionary: 进行字典转模型。

    假如传过来的Jason数据如下所示,注意到age传过来的的key为Age,这样在字典转模型时会报错,因为这个Age在模型类里面并没有定义。

    JSON数据:
    {
        @"name":@"xiaoMing",
        @"sex":@1,
        @"Age":@12
    }
    

    我们可以在Person里重写 setValue:(id)value forUndefinedKey: 方法,这个方法是针对碰到未定义的key时怎么办的方法。

    #import "Person.h"
     
    @implementation Person
     
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key{
       
        if ([key isEqualToString:@"Age"]) {
            [self setValue:value forKey:@"age"];
        }
       
    }
     
    @end
    

    注意点

    • Json里的字段数量和字段名字应该和model类所匹配,Json少了字段不会出现问题,但是多了或者是字段名不对,就会崩溃。

    • 不需对基本数据类型做处理,例如int型转NSNumber,内部会自动作出处理

    • NSArray和NSDictionary等集合对象,value都不能是nil,否则会导致Crash

    异常信息和异常处理

    当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException的异常,并且应用程序Crash

    我们可以重写下面两个方法,根据业务需求合理的处理KVC导致的异常:

    • 在取值时,未有对应的key:
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    
    • 在赋值时,未有对应的key:
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    

    当通过 KVC 给某个非对象的属性赋值为 nil 时,例如使用 setValue:forkey 给 int 或 float 类型属性赋为 nil 时,会抛出 NSInvalidArgumentException 的异常并崩溃。

    我们可以重写下面的方法,进行处理:

    - (void)setNilValueForKey:(NSString *)key;
    

    示例:

    当我们为Person类的NSInteger类型的sex属性赋nil时,会报错

    [myself setValue:nil forKey:@"sex"];
    

    Person.m 重写 setNilValueForKey: 方法

    - (void)setNilValueForKey:(NSString *)key{
     
        if ([key isEqualToString:@"sex"]) {
           
            [self setValue:@1111 forKey:@"sex"];
           
        }else{
           
            [super setNilValueForKey:key];
           
        }
    }
    

    集合属性操作

    当我们要操作一个对象里的集合属性时(NSArray、NSSet等),我们可以通过KVC方法取到集合属性,然后通过集合属性操作集合中的元素。实际开发中最常用的方法,叫做间接操作

    直接操作要实现下面的方法,但通常不会用到:

    有序集合对应方法如下:
     
    -countOf<Key>//必须实现,对应于NSArray的基本方法count:2  -objectIn<Key>AtIndex:
     
    -<key>AtIndexes://这两个必须实现一个,对应于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
     
    -get<Key>:range://不是必须实现的,但实现后可以提高性能,其对应于 NSArray 方法 getObjects:range:
     
    -insertObject:in<Key>AtIndex:
     
    -insert<Key>:atIndexes://两个必须实现一个,类似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
     
    -removeObjectFrom<Key>AtIndex:
     
    -remove<Key>AtIndexes://两个必须实现一个,类似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
     
    -replaceObjectIn<Key>AtIndex:withObject:
     
    -replace<Key>AtIndexes:with<Key>://可选的,如果在此类操作上有性能问题,就需要考虑实现之
    
    无序集合对应方法如下:
     
    -countOf<Key>//必须实现,对应于NSArray的基本方法count:
     
    -objectIn<Key>AtIndex:
     
    -<key>AtIndexes://这两个必须实现一个,对应于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
     
    -get<Key>:range://不是必须实现的,但实现后可以提高性能,其对应于 NSArray 方法 getObjects:range:
     
    -insertObject:in<Key>AtIndex:
     
    -insert<Key>:atIndexes://两个必须实现一个,类似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
     
    -removeObjectFrom<Key>AtIndex:
     
    -remove<Key>AtIndexes://两个必须实现一个,类似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
     
    -replaceObjectIn<Key>AtIndex:withObject:
     
    -replace<Key>AtIndexes:with<Key>://这两个都是可选的,如果在此类操作上有性能问题,就需要考虑实现之
    

    使用 KVC 进行集合类的运算

    KVC 提供的 valueForKeyPath: 方法非常强大,可以在keyPath中嵌套集合运算符对集合中的对象进行相关的运算,例如求一个数组中所有Person对象的age总和。集合对象主要指NSArray和NSSet,不包括NSDictionary。

    集合运算符的格式

    keyPathToCollection.@collentionOperator.keyPathToproperty
    
    • keyPathToCollection:Left key path,要操作的集合对象,若调用 valueForKeyPath: 方法的对象本来就是集合对象,则可以省略;
    • collentionOperator:Collection operator,集合操作符,一般以@开头;
    • keyPathToproperty:Right key path,要运算的属性。
     
    //Address.h
    @interface Address : NSObject
    @property (copy, nonatomic) NSString *city;
    @property (copy, nonatomic) NSString *street;
    @property (strong, nonatomic) NSNumber *cityNumber;
    @property (assign, nonatomic) NSInteger streetNumber;
    @end
     
    
    - (void)viewDidLoad {
        [super viewDidLoad];
       
        NSMutableArray *array = [NSMutableArray array];
       
        for ( int i = 0 ; i < 5 ; i++ ) {
           
            Address *address = [[Address alloc] init];
           
            NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];
           
            //批量赋值
            NSDictionary *dic = @{
                                  @"city":cityStr,
                                  @"street":@"street",
                                  @"cityNumber":@(i),
                                  @"streetNumber":@101
                                  };
           
            [address setValuesForKeysWithDictionary:dic];
           
            [array addObject:address];
        }
       
        //返回数组中保存的对象的属性,返回值为数组
        NSArray *cityArray = [array valueForKeyPath:@"city"];
        NSLog(@"%@",cityArray);
     
    }
     
    

    集合运算符的分类

    集合运算符主要分为三类:

    • 集合操作符:处理集合包含的对象,并根据操作符的不同返回不同的类型,返回值以NSNumber为主。
    • 数组操作符:根据操作符的条件,将符合条件的对象包含在数组中返回。
    • 嵌套操作符:处理集合对象中嵌套其他集合对象的情况,返回结果也是一个集合对象。
    1. 集合操作符

    集合操作符处理 NSArray和 NSSet 及其子类这样的集合对象,并根据不同的操作符返回不同类型的对象,返回值一般都是 NSNumber。

    1. @avg 用来计算集合中 right keyPath 指定的属性的平均值。
    //@avg:平均值
    NSNumber *avg = [array valueForKeyPath:@"@avg.streetNumber"];
    NSLog(@"%@",avg);
    
    1. @count 用来计算集合中对象的数量。备注:@count 操作符比较特殊,它不需要写 right keyPath,即使写了也会被忽略。
    //@count:集合里对象的数量
    NSNumber *count = [array valueForKeyPath:@"@count"];
    NSLog(@"%@",count);
    
    1. @sum 用来计算集合中 right keyPath 指定的属性的总和。
    //@sum:总和
    NSNumber *sum = [array valueForKeyPath:@"@sum.streetNumber"];
    NSLog(@"%@",sum);
    
    1. @max 用来查找集合中 right keyPath 指定的属性的最大值。
    //@max:最大值
    NSNumber *max = [array valueForKeyPath:@"@max.cityNumber"];
    NSLog(@"%@",max);
    
    1. @min 用来查找集合中 right keyPath 指定的属性的最小值。
    //@min:最小值
    NSNumber *min = [array valueForKeyPath:@"@min.cityNumber"];
    NSLog(@"%@",min);
    

    备注:@max 和 @min 在进行判断时,都是通过调用 compare: 方法进行判断,所以可以通过重写该方法对判断过程进行控制。

    2. 数组操作符(因为返回的是数组,所以叫数组操作符)
    1. @unionOfObjects 将集合中的所有对象的同一个属性放在数组中返回。这个和直接写属性好像没区别。
    NSArray *city = [array valueForKeyPath:@"@unionOfObjects.city"];
    NSLog(@"%@",city);
     
    NSArray *cityArray = [array valueForKeyPath:@"city"];
    NSLog(@"%@",cityArray);
    
    1. @distinctUnionOfObjects 将集合中对象的属性进行去重并返回。
    NSArray *streetNumberArr = [array valueForKeyPath:@"@distinctUnionOfObjects.streetNumber"];
    NSLog(@"%@",streetNumberArr);
     
    
    3. 嵌套操作符

    嵌套操作符是对集合里的集合进行操作,比如数组里面的数组。

    NSMutableArray *array1 = [NSMutableArray array];
       
        for ( int i = 0 ; i < 5 ; i++ ) {
           
            Address *address = [[Address alloc] init];
           
            NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];
           
            //批量赋值
            NSDictionary *dic = @{
                                  @"city":cityStr,
                                  @"street":@"street",
                                  @"cityNumber":@(i),
                                  @"streetNumber":@101
                                  };
           
            [address setValuesForKeysWithDictionary:dic];
           
            [array1 addObject:address];
        }
       
        NSMutableArray *array2 = [NSMutableArray array];
       
        for ( int i = 3 ; i < 9 ; i++ ) {
           
            Address *address = [[Address alloc] init];
            
            NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];
           
            //批量赋值
            NSDictionary *dic = @{
                                  @"city":cityStr,
                                  @"street":@"street",
                                  @"cityNumber":@(i),
                                  @"streetNumber":@101
                                  };
           
            [address setValuesForKeysWithDictionary:dic];
           
            [array2 addObject:address];
        }
       
        NSArray *array = @[array1,array2];
    
    1. @unionOfArrays 是用来操作集合内部的集合,将所有right keyPath对应的属性放在一个数组中返回。
    NSArray *result = [array valueForKeyPath:@"@unionOfArrays.city"];
     
    
    1. @distinctUnionOfArrays 是用来操作集合内部的集合对象,将所有right keyPath对应的对象放在一个数组中,并进行排重。
        NSArray *result = [array valueForKeyPath:@"@distinctUnionOfArrays.city"];
    
    1. @distinctUnionOfSets 是用来操作集合内部的集合对象,将所有right keyPath对应的对象放在一个set中,并进行排重。
        NSSet *result = [array valueForKeyPath:@"@distinctUnionOfSets .city"];
    
    4. 小技巧

    如果你想对集合里装的对象直接进行操作,可以将 right keyPath 直接写为 self。比如集合里装的是NSNumber对象,如下:

        NSArray *numbers = @[@1,@1,@3,@5];
     
        NSNumber *sum = [numbers valueForKeyPath:@"@distinctUnionOfObjects.self"];
       
        NSNumber *avg = [numbers valueForKeyPath:@"@avg.self"];
    

    KVC对数值和结构体型属性的支持

    KVC可以自动的将数值或结构体型的数据打包或解包成NSNumber或NSValue对象,以达到适配的目的。

    举个例子,Person类有个NSInteger类型的age属性,如下:

    //  Person.m
    #import "Person.h"
     
    @interface Person ()
     
    @property (nonatomic,assign) NSInteger age;
     
    @end
     
    @implementation Person
     
    @end
     
    //  Person.m
    #import "Person.h"
     
    @interface Person ()
     
    @property (nonatomic,assign) NSInteger age;
     
    @end
     
     
    @implementation Person
     
    @end
    
    1. 修改值

    我们通过KVC技术使用如下方式设置age属性的值:

    [[person setValue:[NSNumber numberWithInteger:5] forKey:@"age"];
    

    我们赋给age的是一个NSNumber对象,KVC会自动的将NSNumber对象转换成NSInteger对象,然后再调用相应的访问器方法设置age的值

    2. 获取值

    同样,以如下方式获取age属性值:

    NSNumber *age = [person valueForKey:@"age"];
    

    这时,会以NSNumber的形式返回age的值

    3. 注意点

    我们不能直接将基本数据类型通过KVC赋值,需要把数据转成NSNumber或NSValue类型传入。

    可以使用NSNumber的数据类型有:

    + (NSNumber*)numberWithChar:(char)value;
    + (NSNumber*)numberWithUnsignedChar:(unsignedchar)value;
    + (NSNumber*)numberWithShort:(short)value;
    + (NSNumber*)numberWithUnsignedShort:(unsignedshort)value;
    + (NSNumber*)numberWithInt:(int)value;
    + (NSNumber*)numberWithUnsignedInt:(unsignedint)value;
    + (NSNumber*)numberWithLong:(long)value;
    + (NSNumber*)numberWithUnsignedLong:(unsignedlong)value;
    + (NSNumber*)numberWithLongLong:(longlong)value;
    + (NSNumber*)numberWithUnsignedLongLong:(unsignedlonglong)value;
    + (NSNumber*)numberWithFloat:(float)value;
    + (NSNumber*)numberWithDouble:(double)value;
    + (NSNumber*)numberWithBool:(BOOL)value;
    + (NSNumber*)numberWithInteger:(NSInteger)valueNS_AVAILABLE(10_5,2_0);
    + (NSNumber*)numberWithUnsignedInteger:(NSUInteger)valueNS_AVAILABLE(10_5,2_0);
    

    可以使用NSValue的数据类型有:

    + (NSValue*)valueWithCGPoint:(CGPoint)point;
    + (NSValue*)valueWithCGSize:(CGSize)size;
    + (NSValue*)valueWithCGRect:(CGRect)rect;
    + (NSValue*)valueWithCGAffineTransform:(CGAffineTransform)transform;
    + (NSValue*)valueWithUIEdgeInsets:(UIEdgeInsets)insets;
    + (NSValue*)valueWithUIOffset:(UIOffset)insets;
    

    NSValue主要用于处理结构体型的数据,任何结构体都是可以转化成NSValue对象的,包括其它自定义的结构体

    属性验证

    在调用 KVC 前可以先进行验证 key(keyPath) 和 value 的正确性,验证通过下面两个方法进行。验证方法默认实现返回 YES,可以通过重写对应的方法修改验证逻辑

    注意:验证方法需要我们手动调用,并不会在进行 KVC 的过程中自动调用

    //key方法
    - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
     
    //keyPath方法
    - (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue
    forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
     
    

    主动去调用验证方法

    Person *person = [[Person alloc] init];
    NSError *error;
    NSString *name = @"xds";
    if (![person validateValue:&name forKey:@"name" error:&error]) {
        NSLog(@"%@", error);
    }
    

    单独验证

    KVC还支持对单独属性做验证,可以通过定义validate<Key>:error:格式的方法,并在方法内部实现验证代码。在编写KVC验证代码的时候,应该先查找属性有没有自定义validate方法,然后再查找validateValue:方法,如果有则调用自己实现的方法,如果两个方法都没有实现则默认返回YES。

    - (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
        if ((*ioValue ** nil) || ([(NSString *)*ioValue length] < 2)) {
            if (outError != NULL) {
                *outError = [NSError errorWithDomain:PersonErrorDomain
                                                code:PersonInvalidNameCode
                                            userInfo:@{ NSLocalizedDescriptionKey
                                                        : @"Name too short" }];
            }
            return NO;
        }
        return YES;
    }
    

    注意:这里 validateName 是 validate + 属性名称 组合而来的。

    KVC 的搜索规则

    在学习KVC的搜索规则前,要先弄明白一个属性的作用,这个属性在搜索过程中起到很重要的作用。这个属性表示是否允许读取实例变量的值,如果为YES则在KVC查找的过程中,从内存中读取属性实例变量的值

    @property (class, readonly) BOOL accessInstanceVariablesDirectly;
    

    在KVC的实现中,依赖setter和getter的方法实现,所以方法命名应该符合苹果要求的规范,否则会导致KVC失败

    基础Getter搜索模式(valueForKey:/valueForKeyPath:)

    • 1.首先按get<Key>、<key>、is<Key>的顺序查找getter方法,找到直接调用。
      • 若方法的返回结果类型为是一个对象指针,则直接返回结果;
      • 若类型为能够转化为NSNumber的基本数据类型,转换为NSNumber后返回;
      • 否则,转换为NSValue返回。
    • 2.上面的getter没有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。
      如果countOf<Key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的集合代理(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes这几个方法组合的形式调用。如果receiver的类实现了get<Key>:range:方法,给方法也会用于性能优化。
    • 3.还没查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的集合代理(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:组合的形式调用。
    • 4.还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,_is<Key>,<key>,is<Key>(注意大小写)的顺序直接搜索实例变量。如果搜索到了,则返回receiver相应实例变量的值。返回结果的处理见步骤1。
    • 5.再没查到,调用valueForUndefinedKey:方法,报出异常。

    总结一下:

    1. 先找相应的 getter 方法(get<Key>, <key>, is<Key>, 或者 _<key>),找到了则返回(对象类型直接返回,其它类型进行转换);
    2. 没有找到,则寻找 NSArray 相应的方法;
    3. 没有找到,则寻找 NSSet 相应的方法;
    4. 如果还没有,且 accessInstanceVariablesDirectly 类属性返回的是 YES,则去搜索实例变量(_<key>、_is<Key>、<key>、is<Key>)。如果发现了,则返回;
    5. 还没有,则转到 valueForUndefinedKey: 方法并抛出异常。

    基础Setter搜索模式

    这是setValue:forKey:的默认实现,给定输入参数value和key。试图在接收调用对象的内部,设置属性名为key的value,通过下面的步骤:

    • 查找set<Key>:或_set<Key>命名的setter,按照这个顺序,如果找到的话,调用这个方法并将值传进去(根据需要进行对象转换)。

    • 如果没有发现一个简单的setter,但是 accessInstanceVariablesDirectly 类属性返回YES,则查找一个命名规则为_<key>、_is<Key>、<key>、is<Key>的实例变量。根据这个顺序,如果发现则将value赋值给实例变量。

    • 如果没有发现setter或实例变量,则调用setValue:forUndefinedKey:方法,并默认提出一个异常,但是一个NSObject的子类可以提出合适的行为。

    总结:

      1. 先找 setter 方法(set<Key>:或_set<Key>);
      1. 没找到,如果则 accessInstanceVariablesDirectly 类属性返回的是 YES,则去查找实例变量(_<key>、_is<Key>、<key>、is<Key>),若找到,则赋值;
      1. 没有找到 setter 方法和实例变量,则转到 setValue:forUndefinedKey: 方法,并抛出异常。

    KVC性能

    根据上面KVC的实现原理,我们可以看出KVC的性能并不如直接访问属性快,虽然这个性能消耗是微乎其微的。所以在使用KVC的时候,建议最好不要手动设置属性的setter、getter,这样会导致搜索步骤变长

    而且尽量不要用KVC进行集合操作,例如NSArray、NSSet之类的,集合操作的性能消耗更大,而且还会创建不必要的对象

    私有访问

    根据上面的实现原理我们知道,KVC本质上是操作方法列表以及在内存中查找实例变量我们可以利用这个特性访问类的私有变量,例如下面在.m中定义的私有成员变量和属性,都可以通过KVC的方式访问。

    这个操作对readonly的属性,@protected的成员变量,都可以正常访问。如果不想让外界访问类的成员变量,则可以将accessInstanceVariablesDirectlygetter方法返回为NO

    Person.m文件
     
    @interface Person(){
        NSString *str;
    }
    @property (strong, nonatomic) NSString *testStr;
    @end
    

    testStr是私有属性,str是私有成员变量。使用KVC都可以访问到这两个。如果不想让外界访问到str成员变量,可以这样做:

    @implementation Person
     
    + (BOOL)accessInstanceVariablesDirectly{
        return NO;
    }
    @end
    

    这样就访问不到私有的成员变量了。但是还是能访问到私有的属性,因为属性有getter和setter方法,KVC会先搜索访问方法,再去看accessInstanceVariablesDirectlygetter能不能访问实例变量。

    KVC 的实践

    KVC在实践中也有很多用处,例如UITabbar或UIPageControl这样的控件,系统已经为我们封装好了,但是对于一些样式的改变并没有提供足够的API,这种情况就需要我们用KVC进行操作了。可以自定义一个UITabbar对象,然后在内部创建自己想要的视图,并通过layoutSubviews方法在内部进行重新布局。然后通过KVC的方式,将UITabbarController的tabbar属性替换为自定义的类即可。

    安全性检查

    KVC存在一个问题在于,因为传入的key或keyPath是一个字符串,这样很容易写错或者属性自身修改后字符串忘记修改,这样会导致Crash。

    可以利用iOS的反射机制来规避这个问题,通过@selector()获取到方法的SEL,然后通过NSStringFromSelector()将SEL反射为字符串。这样在@selector()中传入方法名的过程中,编译器会有合法性检查,如果方法不存在或未实现会报黄色警告。

    相关文章

      网友评论

        本文标题:iOS 关于KVC的一些总结

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