iOS KVC小结

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

推荐阅读更多精彩内容

  • 本文参考: KVC官方文档 KVC原理剖析 iOS KVC详解 KVC 简介 KVC全称是Key Value Co...
    拧发条鸟xds阅读 5,284评论 6 23
  • 原文:iOS 关于KVC的一些总结 本文参考: KVC官方文档 KVC原理剖析 iOS KVC详解 KVC 简介 ...
    liyoucheng2014阅读 935评论 0 3
  • 本文对 KVC、KVO 相关知识进行全面的整理总结,介绍了相关的基本概念、使用方法、注意事项、实现原理等。后续如有...
    z4ywzrq阅读 514评论 0 2
  • KVC 简介 KVC全称是Key Value Coding(键值编码),是一个基于NSKeyValueCoding...
    rightmost阅读 816评论 0 0
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    祀梦_阅读 924评论 0 7