KVC使用详解

作者: 不吃香菜11 | 来源:发表于2020-10-13 00:18 被阅读0次

    KVC

    KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。很多高级的iOS开发技巧都是基于KVC实现的。
    下面是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来设值
    

    KVC中其他比较重要的方法

    + (BOOL)accessInstanceVariablesDirectly;
    //默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    //如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
    
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    //和上一个方法一样,但这个方法是设值。
    
    - (void)setNilValueForKey:(NSString *)key;
    //如果你在SetValue方法时面给Value传nil,则会调用这个方法
    
    - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
    //输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
    

    KVC设值

    KVC要设值,那么就要对象中对应的key,KVC在内部是按什么样的顺序来寻找key的。当调用setValue:属性值 forKey:@”name“的代码时,底层的执行机制如下:

    • 程序优先调用set:属性值方法,代码通过setter方法完成设置。

    • 如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。

    • 如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量。

    • 和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。

    • 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

    简单来说就是如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作。如果开发者想让这个类禁用KVC里,那么重写+(BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUndefinedKey:方法。

    KVC取值

    valueForKey:的搜索方式:
    1、首先按getKey,key,isKey的顺序查找getter方法,找到直接调用。如果是BOOL、int等内建值类型,会做NSNumber的转换。

    2、上面的getter没找到,查找countOfKey、objectInKeyAtindex、KeyAtindexes格式的方法。如果countOfKey和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法。

    3、还没找到,查找countOfKey、enumeratorOfKey、memberOfKey格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合。
    4、还是没找到,如果类方法accessInstanceVariablesDirectly返回YES。那么按 _key,_isKey,key,iskey的顺序搜索成员名。

    5、再没找到,调用valueForUndefinedKey。

    KVC使用keypath

    在开发过程中,一个类的成员变量有可能是自定义类或其他的复杂数据类型,你可以先用KVC获取该属性,然后再次用KVC来获取这个自定义类的属性,但这样是比较繁琐的,对此,KVC提供了一个解决方案,那就是键路径keyPath。顾名思义,就是按照路径寻找key。

    - (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通过KeyPath来取值
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通过KeyPath来设值
    

    例子

            //Test生成对象
            Test *test = [[Test alloc] init];
            //Test1生成对象
            Test1 *test1 = [[Test1 alloc] init];
            //通过KVC设值test的"test1"
            [test setValue:test1 forKey:@"test1"];
            //通过KVC设值test的"test1的name"
            [test setValue:@"xiaoming" forKeyPath:@"test1.name"];
            //通过KVC取值age打印
            NSLog(@"test的\"test1的name\"是%@", [test valueForKeyPath:@"test1.name"]);
    

    KVC对于keyPath是搜索机制第一步就是分离key,用小数点.来分割key,然后再像普通key一样按照先前介绍的顺序搜索下去。

    KVC处理异常

    KVC中最常见的异常就是不小心使用了错误的key,或者在设值中不小心传递了nil的值,KVC中有专门的方法来处理这些异常。

    KVC处理nil异常

    通常情况下,KVC不允许你要在调用setValue:属性值 forKey:(或者keyPath)时对非对象传递一个nil的值。很简单,因为值类型是不能为nil的。如果你不小心传了,KVC会调用setNilValueForKey:方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。

    KVC处理UndefinedKey异常

    通常情况下,KVC不允许你要在调用setValue:属性值 forKey:(或者keyPath)时对不存在的key进行操作。 不然,会报错forUndefinedKey发生崩溃,重写forUndefinedKey方法避免崩溃。

    KVC键值验证(Key-Value Validation)

    KVC提供了验证Key对应的Value是否可用的方法:(如果我们需要验证Value则需要重写如下方法)

    - (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;
    

    例子

    @interface Test: NSObject {
        NSUInteger _age;
    }
    
    @end
    
    @implementation Test
    
    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
        NSNumber *age = *ioValue;
        if (age.integerValue == 10) {
            return NO;
        }
        
        return YES;
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            //Test生成对象
            Test *test = [[Test alloc] init];
            //通过KVC设值test的age
            NSNumber *age = @10;
            NSError* error;
            NSString *key = @"age";
            BOOL isValid = [test validateValue:&age forKey:key error:&error];
            if (isValid) {
                NSLog(@"键值匹配");
                [test setValue:age forKey:key];
            }
            else {
                NSLog(@"键值不匹配");
            }
            //通过KVC取值age打印
            NSLog(@"test的年龄是%@", [test valueForKey:@"age"]);
            
        }
        return 0;
    }
    

    这样就给了我们一次纠错的机会。需要指出的是,KVC是不会自动调用键值验证方法的,就是说我们如果想要键值验证则需要手动验证。但是有些技术,比如CoreData会自动调用。

    KVC处理集合

    简单集合运算符

    简单集合运算符共有@avg, @count , @max , @min ,@sum5种

    @interface Book : NSObject
    @property (nonatomic, copy)  NSString* name;
    @property (nonatomic, assign)  CGFloat price;
    @end
    
    @implementation Book
    @end
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            Book *book1 = [Book new];
            book1.name = @"The Great Gastby";
            book1.price = 10;
            Book *book2 = [Book new];
            book2.name = @"Time History";
            book2.price = 20;
            Book *book3 = [Book new];
            book3.name = @"Wrong Hole";
            book3.price = 30;
            
            Book *book4 = [Book new];
            book4.name = @"Wrong Hole";
            book4.price = 40;
            
            NSArray* arrBooks = @[book1,book2,book3,book4];
            NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];
            NSLog(@"sum:%f",sum.floatValue);
            NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];
            NSLog(@"avg:%f",avg.floatValue);
            NSNumber* count = [arrBooks valueForKeyPath:@"@count"];
            NSLog(@"count:%f",count.floatValue);
            NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"];
            NSLog(@"min:%f",min.floatValue);
            NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"];
            NSLog(@"max:%f",max.floatValue);
            
        }
        return 0;
    }
    
    打印结果:
    sum:100.000000
    avg:25.000000
    count:4.000000
    min:10.000000
    max:40.000000
    
    对象运算符

    比集合运算符稍微复杂,能以数组的方式返回指定的内容,一共有两种:

    • @distinctUnionOfObjects
    • @unionOfObjects

    它们的返回值都是NSArray,区别是前者返回的元素都是唯一的,是去重以后的结果;后者返回的元素是全集。

    KVC处理字典

    KVC里面还有两个关于NSDictionary的方法:

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

    dictionaryWithValuesForKeys:是指输入一组key,返回这组key对应的属性,再组成一个字典。 setValuesForKeysWithDictionary是用来修改Model中对应key的属性。
    KVC使用优点
    KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能。 下面列举iOS开发中KVC的使用场景.

    1.动态地取值和设值:利用KVC动态的取值和设值是最基本的用途了。
    2.用KVC来访问和修改私有变量
    3.Model和字典转换
    4.修改一些控件的内部属性

    这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些控件的API,这样我们就无法正常地访问和修改这些控件的样式。
    而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了。

    5.快捷操作集合

    KVC原理

    设值过程

    ①.按set<Key>:_set<Key>:顺序查找对象中是否有对应的方法

    • 找到了直接调用设值

    • 没有找到跳转第2步

    ②.判断accessInstanceVariablesDirectly结果(判断是否可以直接访问成员变量

    • 为YES时按照_<key>_is<Key><key>is<Key>的顺序查找成员变量,找到了就赋值;找不到就跳转第3步

    • 为NO时跳转第3步

    ③.调用setValue:forUndefinedKey:。默认情况下会引发一个异常,但是继承于NSObject的子类可以重写该方法就可以避免崩溃并做出相应措施

    image.png

    取值过程

    ①.按照get<Key><key>is<Key>_<key>顺序查找对象中是否有对应的方法

    • 如果有则调用getter,执行第5步

    • 如果没有找到,跳转到第2步

    ②.查找是否有countOf<Key>objectIn<Key>AtIndex: 方法(对应于NSArray类定义的原始方法)以及<key>AtIndexes: 方法(对应于NSArray方法objectsAtIndexes:)

    • 如果找到其中的第一个(countOf<Key>),再找到其他两个中的至少一个,则创建一个响应所有 NSArray方法的代理集合对象,并返回该对象(即要么是countOf<Key> + objectIn<Key>AtIndex:,要么是countOf<Key> + <key>AtIndexes:,要么是countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:)

    • 如果没有找到,跳转到第3步

    ③.查找名为countOf<Key>enumeratorOf<Key>memberOf<Key>这三个方法(对应于NSSet类定义的原始方法)

    • 如果找到这三个方法,则创建一个响应所有NSSet方法的代理集合对象,并返回该对象

    • 如果没有找到,跳转到第4步

    ④.判断accessInstanceVariablesDirectly

    • 为YES时按照_<key>_is<Key><key>is<Key>的顺序查找成员变量,找到了就取值

    • 为NO时跳转第6步

    ⑤.判断取出的属性值

    • 属性值是对象,直接返回

    • 属性值不是对象,但是可以转化为NSNumber类型,则将属性值转化为NSNumber 类型返回

    • 属性值不是对象,也不能转化为NSNumber类型,则将属性值转化为NSValue类型返回

    ⑥.调用valueForUndefinedKey:.默认情况下会引发一个异常,但是继承于NSObject的子类可以重写该方法就可以避免崩溃并做出相应措施

    image.png

    相关文章

      网友评论

        本文标题:KVC使用详解

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