美文网首页
iOS KVC小结

iOS KVC小结

作者: 心中有光啊 | 来源:发表于2022-02-14 10:07 被阅读0次

iOS KVC小结

KVC的概念

KVC,即Key-value coding,键值编码。给我们提供了一套更加直接的方式,来访问一个对象的属性,或者给对象的属性赋值。而不是通过setter和getter方法。是一种可以直接通过名字或者key来获取对象的属性的机制。KVC的功能很强大,也属于iOS的黑魔法之一。类的只读属性或者私有属性(iOS 13以后,一些控件的私有属性就不能获取了,运行时会报错;但是自定义的类,用代码测试还可以使用),只要能获取到属性的名字,使用KVC都可以进行值的操作。

成员变量、属性

开始之前,先来回忆一下成员变量、属性的概念,以便于对后面的KVC的流程有更清楚的理解。

    @interface KVCPerson : NSObject
    {
        //成员变量
        NSString * _name;
    }
    //属性
    @property (nonatomic, copy) NSString * address;
    @end

创建了一个KVCPerson类,代表一个人。一个人有名字、家庭住址。所以我们把名字添加为“成员变量”,把家庭住址添加为“属性”。

  • 在大括号{}中声明的变量都是成员变量。成员变量不会自动生成settergetter方法。

    • 成员变量的取值/赋值,使用->。例如:p->name
  • OC中用@property来声明一个属性,编译器会自动为属性生成“成员变量”,并且为成员变量生成对应的settergetter方法。

    • 属性可以使用“点语法”进行操作。例如:p.name

    • 获取KVCPerson类的IvarList和MethodList展示如下:

    //成员变量列表  
    mIvarList ==
        (
         "_name",
         "_address"
        )
    //方法列表
    mMthodList ==
         (
         getIvarList,
         getMethodList,
         //自动生成的address的getter方法
         address,
         //自动生成的address的setter方法
         "setAddress:",
         init,
         ".cxx_destruct"
        )

KVC常用的API

设置值的方法

    //给定一个值和一个属性的键名,设置该属性的值。
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    
    //给定一个值和一个“属性的属性”的键名,设置该“属性的属性”的值。like:address.city。
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    
    //处理设置值的时候没有找到键名的异常情况的API。找不到对应 key 命名的属性时,就会 NSUnknownKeyException 异常崩溃,可以在对象里重写下面两个方法,防止崩溃。
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    
    //如果将非对象类型的属性设置为 nil的情况下,会报NSInvalidArgumentException 的异常;可以使用这个方法来处理异常,防止崩溃
    - (void)setNilValueForKey:(NSString *)key;

在给属性赋值的时候,KVC不会像settergetter方法那么温柔,对于类的只读属性或者私有属性,settergetter是访问不到的。
相比之下,KVC就会显得比较粗暴。一个类的只读属性、私有属性,都可以进行赋值操作。

获取值的方法

    //给定一个键名,获取到该键对应的值
    - (nullable id)valueForKey:(NSString *)key; 
    
    //给一个“属性的属性”的键名,获取到该“属性的属性”的值。这个方法结合操作符做到一些比较好玩的事情
    - (nullable id)valueForKeyPath:(NSString *)keyPath; 
    
    //获取值操作的情况下,没有找到键名,会抛出异常。在类中可以重写这个方法,防止程序的崩溃。
    - (nullable id)valueForUndefinedKey:(NSString *)key;

没有setter/getter等方法情况下,根据这个方法的返回决定是否直接查询成员变量

     //在找不到属性的setter/getter方法的情况下,是否直接去查询属性的成员变量。默认返回yes
    + (BOOL)accessInstanceVariablesDirectly;

属性值正确性验证

    //属性的正确性验证,默认返回yes。可以对一个属性进行验证,如果符合逻辑条件就可以返回yes,否则返回false。
    - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
使用案例:

验证年龄的合理性,如果年龄超过200岁就不合情理了,所以可以对年龄加上正确性验证。在KVCPerson类中增加一个属性age,并添加验证代码。

    //正确性验证
    - (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError{
     NSNumber* value = (NSNumber*)*ioValue;
     if ([value integerValue] >= 0 && [value integerValue] <= 200) {
         return YES;
      }
     return NO;
    }

在使用KVCPerson类的时候,添加年龄的验证逻辑代码

    KVCPerson * p = [[KVCPerson alloc] init];
    NSNumber * value = @200;
    if ([p validateValue:&value forKey:@"age" error:NULL]) {
         NSLog(@"我的数据不正确");
    }

KVC赋值的流程顺序

在调用- (void)setValue:(nullable id)value forKey:(NSString *)key方法的时候,执行的流程如下:

  • 查询类中的有没有相关的方法实现:

    • 查询类中有没有set<key>:(NSString *)key的方法。有的话通过setter方法给属性设值;否则继续查找。

    • 查询类中有没有_set<key>:(NSString *)key的方法。有的话通过setter方法给属性设值;否则继续查找。

    • 查询类中有没有setIs<key>:(NSString *)key的方法。有的话通过setter方法给属性设值;没有的话,需要去查询accessInstanceVariablesDirectly的返回值。

  • 没有查询到上述的方法的情况下,会去查询accessInstanceVariablesDirectly这个方法的返回值(没有重写的话,默认返回YES)。

    • 如果该方法的返回值是NO的情况下,会抛出NSUnknownKeyException异常

    • 如果该方法的返回值是YES的情况下,会去找类中的成员变量,并且按照一定的优先级顺序先匹配成员变量。获取到匹配的成员变量后,直接赋值。

  • 没有找到对应的setter方法,直接给成员变量赋值的流程

    • 按照优先级顺序进行匹配成员变量:_<key> > _is<Key> > <key> > is<Key>

    • 按照顺序查询到一个能匹配的成员变量后,就给该成员变量赋值。

    • 假设以上四种形式的成员变量都存在,只匹配优先级最高的一个。

流程图显示如下:


setvalue.jpg

KVC取值的流程顺序

调用- (nullable id)valueForKey:(NSString *)key方法的时候,执行的流程如下:

  • 查询类的方法列表中有没有相关的getter方法实现

    • 查询类中有没有- (NSString *)get<Key>方法。有的话通过getter方法取出属性的值;否则继续查找。

    • 查询类中有没有- (NSString *)<key>方法。有的话通过getter方法取出属性的值;否则继续查找。

    • 查询类中有没有- (NSString *)is<Key>方法。有的话通过getter方法取出属性的值;否则继续查找。

    • 查询类中有没有- (NSString *)_<key>方法。有的话通过getter方法取出属性的值;没有的话,需要去查询accessInstanceVariablesDirectly的返回值。

  • 没有查询到上述的方法的情况下,会去查询accessInstanceVariablesDirectly这个方法的返回值(没有重写的话,默认返回YES)

    • 如果该方法的返回值是NO的情况下,会抛出NSUnknownKeyException异常

    • 如果该方法的返回值是YES的情况下,会去找类中的成员变量,并且按照一定的优先级顺序先匹配成员变量。获取到匹配的成员变量后,取出该成员变量的值。

  • 没有找到对应的getter方法,就需要直接查询匹配的成员变量,然后取出该成员变量的值。

    • 按照优先级顺序进行匹配成员变量:_<key> > _is<Key> > <key> > is<Key>

    • 按照顺序查询到一个能匹配的成员变量后,就取出该成员变量的值。

    • 假设以上四种形式的成员变量都存在,只匹配优先级最高的一个。

流程图显示如下:


valueforkey.png

KVC集合操作符

在学习KVC的时候,看到了KVC集合操作符,眼前一亮啊。所以,为了以后能够随时看一下,在这里进行记录。

KVC集合操作符允许在valueForKeyPath:方法中使用操作运算,作用于集合中所有的元素,来获取到想要的成果。

集合操作符根据其返回值的不同,分为三个类型:

  • 简单的集合操作符,返回的是 strings, number, 或者 dates
    * @count: 返回的值为集合中对象总数,是`NSNumber`类型数据。
    * @sum  : 首先把集合中的每个对象都转换为`double`类型,然后计算其总和,最后返回值为这个总和的`NSNumber`对象。
    * @avg  : 把集合中的每个对象都转换为`double`类型,然后计算其平均值,返回值为平均值的`NSNumber`对象。
    * @max  : 使用`compare:`方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。
    * @min  : 和`@max`一样,但是返回的是集合中的最小值。
  • 对象操作符,返回的是一个数组
    @unionOfObjects / @distinctUnionOfObjects: 返回一个由操作符右边的 key path 所指定的对象属性组成的数组。两个方法中@distinctUnionOfObjects会对数组去重, 而且@unionOfObjects不会.</pre>
  • 数组和集合操作符, 返回的是一个数组或者集合
    @distinctUnionOfArrays / @unionOfArrays: 返回了一个数组,其中包含这个集合中每个数组对于这个操作符右面指定的 key path 进行操作之后的值。正如你期望的,distinct版本会移除重复的值。
    
    @distinctUnionOfSets: 和@distinctUnionOfArrays差不多, 但是它期望的是一个包含着NSSet对象的NSSet,并且会返回一个NSSet对象。因为集合不能包含重复的值,所以它只有distinct操作。</pre>

事例数据:

name price date
iPhone 5 199 September 21, 2012
iPad Mini 329 November 2, 2012
MacBook Pro 1699 June 11, 2012
iMac 1299 November 2, 2012

创建一个类,类名为Product,如下。把以上数据包装成对象,保存到productArray数组中。

@interface Product : NSObject
//产品名称
@property (nonatomic, copy) NSString * name;
//产品价格
@property (nonatomic, assign) int price;
//产品的上市时间
@property (nonatomic, strong) NSDate * date;
@end
  • 简单的集合操作符,应用实例:
        NSString * count = [self.productArray valueForKeyPath:@"@count"]; 
        NSString * sum_price = [self.productArray valueForKeyPath:@"@sum.price"]; 
        NSString * avg_pric = [self.productArray valueForKeyPath:@"@avg.price"]; 
        NSString * max_price = [self.productArray valueForKeyPath:@"@max.price"]; 
        NSString * min_date = [self.productArray valueForKeyPath:@"@min.date"]; 

        NSLog(@"count is %@",count);            -----> count is 4
        NSLog(@"sum_price is %@",sum_price);    -----> sum_price is 3526
        NSLog(@"avg_pric is %@",avg_pric);      -----> avg_pric is 881.5
        NSLog(@"max_price is %@",max_price);    -----> max_price is 1699
        NSLog(@"min_date is %@",min_date);      -----> min_date is June 11, 2012
  • 对象操作符,应用实例(在productArray数组中,使用第一条数据重复生成多次对象,能看到两个方法的区别):
        //@unionOfObjects方法,不会对数组去重
        NSArray *unionOfObjects = [self.productArray valueForKeyPath:@"@unionOfObjects.name"];
        //@distinctUnionOfObjects 会对数组去重
        NSArray *distinctUnionOfObjects = [self.productArray valueForKeyPath:@"@distinctUnionOfObjects.name"];
        //打印数据
        NSLog(@"unionOfObjects is %@",unionOfObjects);
        NSLog(@"distinctUnionOfObjects is %@",distinctUnionOfObjects);
        -----------------------------------------------------------------------------------------------------
         unionOfObjects is (
          "iPhone 5",
          "iPhone 5",
          "iPhone 5",
          "iPad Mini",
          "MacBook Pro",
          iMac
        )

        distinctUnionOfObjects is (
          iMac,
          "iPad Mini",
          "MacBook Pro",
          "iPhone 5"
        )
  • 数组和集合操作符, 应用实例。(再复制一份商品数据,稍加修改后,包装成对象存放在一个数组中。并与存放上份数据的数组放在同一个数组中。)。集合运算符与数组操作法是相似的,此处不做赘述。
        //把两份产品数据存放到同一个数组中
        NSArray *arraysInArray = @[self.productArray, newProductArray];
        //@unionOfArrays方法,不会对数据去重
        NSArray *unionOfArrays = [arraysInArray valueForKeyPath:@"@unionOfArrays.name"];
        //@distinctUnionOfArrays方法,会对获取到的数据去重
        NSArray *distinctUnionOfArrays = [arraysInArray valueForKeyPath:@"@distinctUnionOfArrays.name"];
        //打印数据
        NSLog(@"unionOfArrays is %@",unionOfArrays);
        NSLog(@"distinctUnionOfArrays is %@",distinctUnionOfArrays);
            -----------------------------------------------------------------------------------------------
        unionOfArrays is (
            "iPhone 5",
            "iPhone 5",
            "iPhone 5",
            "iPad Mini",
            "MacBook Pro",
            iMac,
            "iPhone 5 - 1",
            "iPhone 5 - 1",
            "iPhone 5 - 1",
            "iPad Mini - 1",
            "MacBook Pro - 1",
            "iMac - 1"
        ),
        distinctUnionOfArrays is (
            "iPhone 5 - 1",
            "iPad Mini - 1",
            "iPhone 5",
            "iPad Mini",
            "iMac - 1",
            "MacBook Pro - 1",
            "MacBook Pro",
            iMac
        )

相关文章

网友评论

      本文标题:iOS KVC小结

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