iOS 关于KVC的一些总结

本文参考:

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()中传入方法名的过程中,编译器会有合法性检查,如果方法不存在或未实现会报黄色警告。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容