美文网首页
(IOS)KVC

(IOS)KVC

作者: rightmost | 来源:发表于2018-12-29 17:04 被阅读0次

    KVC 简介

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

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

    在实现了访问器方法的类中,使用点语法和KVC访问对象其实差别不大,二者可以任意混用。但是没有访问器方法的类中,点语法无法使用,这时KVC就有优势了。

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

    KVC 通用的访问方法

    1.通用的访问方法:

    getter方法:valueForKey:

    setter方法:setValue:forKey:

    2.衍生的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。

    @avg 用来计算集合中 right keyPath 指定的属性的平均值。

    //@avg:平均值

    NSNumber *avg = [array valueForKeyPath:@"@avg.streetNumber"];

    NSLog(@"%@",avg);

    @count 用来计算集合中对象的数量。备注:@count 操作符比较特殊,它不需要写 right keyPath,即使写了也会被忽略。

    //@count:集合里对象的数量

    NSNumber *count = [array valueForKeyPath:@"@count"];

    NSLog(@"%@",count);

    @sum 用来计算集合中 right keyPath 指定的属性的总和。

    //@sum:总和

    NSNumber *sum = [array valueForKeyPath:@"@sum.streetNumber"];

    NSLog(@"%@",sum);

    @max 用来查找集合中 right keyPath 指定的属性的最大值。

    //@max:最大值

    NSNumber *max = [array valueForKeyPath:@"@max.cityNumber"];

    NSLog(@"%@",max);

    @min 用来查找集合中 right keyPath 指定的属性的最小值。

    //@min:最小值

    NSNumber *min = [array valueForKeyPath:@"@min.cityNumber"];

    NSLog(@"%@",min);

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

    2. 数组操作符(因为返回的是数组,所以叫数组操作符)

    @unionOfObjects将集合中的所有对象的同一个属性放在数组中返回。这个和直接写属性好像没区别。

    NSArray *city = [array valueForKeyPath:@"@unionOfObjects.city"];

    NSLog(@"%@",city);

    NSArray *cityArray = [array valueForKeyPath:@"city"];

    NSLog(@"%@",cityArray);

    @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];

    @unionOfArrays 是用来操作集合内部的集合,将所有right keyPath对应的属性放在一个数组中返回。

    NSArray *result = [array valueForKeyPath:@"@unionOfArrays.city"];

    @distinctUnionOfArrays 是用来操作集合内部的集合对象,将所有right keyPath对应的对象放在一个数组中,并进行排重。

        NSArray *result = [array valueForKeyPath:@"@distinctUnionOfArrays.city"];

    @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:方法,报出异常。

    总结一下:

    先找相应的 getter 方法(get<Key>, <key>, is<Key>, 或者 _<key>),找到了则返回(对象类型直接返回,其它类型进行转换);

    没有找到,则寻找 NSArray 相应的方法;

    没有找到,则寻找 NSSet 相应的方法;

    如果还没有,且 accessInstanceVariablesDirectly 类属性返回的是 YES,则去搜索实例变量(_<key>、_is<Key>、<key>、is<Key>)。如果发现了,则返回;

    还没有,则转到 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的子类可以提出合适的行为。

    总结:

    先找 setter 方法(set<Key>:或_set<Key>);

    没找到,如果则 accessInstanceVariablesDirectly 类属性返回的是 YES,则去查找实例变量(_<key>、_is<Key>、<key>、is<Key>),若找到,则赋值;

    没有找到 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/okjplqtx.html