iOS KVC详解

关于KVC的详细介绍可以参考官方文档

键值编码(KVC)是由NSKeyValueCoding非正式协议启用的一种机制,对象采用这种机制来提供对其属性的间接访问。当对象符合键值编码时,可以通过简洁,统一的消息传递接口通过字符串参数来访问其属性.

简单使用

需要遵守NSKeyValueCoding协议,直接或者间接继承自NSObject的类苹果提供了默认实现.

  1. 使用键获取属性值

简单属性,值对象

- (nullable id)valueForKey:(NSString *)key

[person valueForKey :@"age"];

有层级(属性的属性)

- (nullable id)valueForKeyPath:(NSString *)keyPath

[person valueForKeyPath :@"address.street"];

批量读取,根据传入的一组keys,依次调用valueForKey,返回一个以keys每个key为键的字典

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys

[person dictionaryWithValuesForKeys:@[@"name",@"age",@"hobby"]]
  1. 使用键设置属性值

注意:设置值的时候数据类型要和原来的一致,因为编译器不会提示这个错误

- (void)setValue:(nullable id)value forKey:(NSString *)key

[person setValue:@(18) forKey:@"age"];

有层级

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath

[person setValue:@"Sanlitun" forKeyPath:@"address.street"];

批量设置属性,这个方法会根据传入的字典参数,依次调用每个keysetValue:方法

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues

[person setValuesForKeysWithDictionary:@{@"name":@"sanhe",
                                            @"age":@(24),
                                            @"hobby":@[@"羽毛球",@"高尔夫球"]
        }];
  1. 集合类型

3.1以数组为例:

如果使用某个键值来访问一个数组(NSArray),他实际上会查询相应数组中的每个对象,然后将查询结果打包成另一个数组并返回给你。

使用valueForKey:和setValue:forKey:(或其等效键路径)的任何其他对象一样,获取或设置集合对象

NSArray *newArray = [p valueForKey:@"hobby"];
newArray = @[@"羽毛球",@"高尔夫球"];
[person setValue:newArray forKey:@"hobby"];

使用协议提供的方法mutableArrayValueForKey:

此方法的默认实现与-valueForKey:识别相同的简单访问器方法和数组访问器方法,并遵循相同的直接实例变量访问策略,但是始终返回可变的集合代理对象,而不是-valueForKey:的不可变集合

NSArray *newArray = [person mutableArrayValueForKey:@"hobby"];
newArray[1] = @"高尔夫球";
[person setValue:newArray forKey:@"hobby"];

如果有层级就用对应的keyPath方法

3.2集合操作符

对象调用valueForKeyPath:时,可以在KeyPath中嵌入一个集合操作符。集合操作符是一个小的关键字列表,前面有一个@,它指定了getter应该执行的操作,以便在返回数据之前以某种方式对数据进行操作。valueForKeyPath的默认实现由NSObject提供

1.集合运算符
@avg,@count,@max,@min平均值,计数,最大值,最小值

聚合运算符可对数组或一组属性进行操作,从而生成一个反映集合某些方面的单个值。

NSMutableArray *pArray = [NSMutableArray arrayWithCapacity:10];
...
[pArray valueForKeyPath:@"@sum.age"]

2.数组运算符
返回操作对象指定属性的集合

数组中包含nil,会引发异常

@unionOfObjects返回数组
@distinctUnionOfObjects返回数组,去重

//返回的数组里的值,为age
[pArray valueForKeyPath:@"@unionOfObjects.age"]

3.嵌套运算符

嵌套运算符对嵌套集合进行操作,其中集合本身的每个条目都包含一个集合。

@distinctUnionOfArrays返回数组,去重
@unionOfArrays,返回数组
@distinctUnionOfSets返回集合,去重

  1. 访问非对象属性

KVC默认实现了非对象属性和对象属性之间的转换,

取值时如果返回不是对象,则getter使用该值初始化一个NSNumber对象(用于标量)或NSValue对象(用于结构),然后返回该值。
类似地,默认情况setValue:forKey:下,给定特定key,如果数据类型不是对象,则设置器首先将适当的<type> Value消息发送到传入值对象以提取基础数据,然后存储该数据。

自定义结构体类型:

typedef struct{
    double longitude;
    double latitude;
}Coordinates;

设置值

Person *p = [[Person alloc]init];
Coordinates coor = {121,17};
NSValue *value = [NSValue valueWithBytes:&coor objCType:@encode(Coordinates)];
[p setValue:value forKey:@"coordinates"];

读取值

NSValue *result = [p valueForKey:@"coordinates"];
Coordinates rCoor;
[result getValue:&rCoor];
  1. nil值的设置

对象类型,设置nil值仍然可用

[p setValue:nil forKey:@"age"];
 NSLog(@"%@",[p valueForKey:@"age"]);
kvcDemo[4470:510628] (null)

非对象类型设置nil

  • 注意:非对象类型通过setValue:设置nil值, 会执行setNilValueForKey:。 此方法的默认实现引发NSInvalidArgumentException异常,但是子类可以覆盖此行为

运行时会报错

[p setValue:nil forKey:@"coordinates"];

搜索模式

获取值

valueForKey:根据传入的key从调用它的类内部执行以下过程

  1. 找访问器方法:get<Key>,<key>,is<Key>,或者_<key>如果找到了,看第五步

  2. 上一步没有找到,接下来搜索countOf<Key>objectIn<Key>AtIndex:,objectsAtIndexes:(看是不是数组)
    (1) 如果找到其中的第一个,再找到其他两个中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并将其返回。否则,请继续执行步骤3。
    (2) 代理对象随后将任何NSArray接收到的一些组合的消息countOf<Key>,objectIn<Key>AtIndex:<key>AtIndexes:消息给键-值编码创建它兼容的对象。如果原始对象还实现了名称为的可选方法get<Key>:range:,则代理对象也会在适当时使用该方法。实际上,代理对象与与键值编码兼容的对象一起使用,使基础属性的行为就好像它是NSArray,即使不是。

  3. 上一步没有找到,接下来找countOf<Key>,enumeratorOf<Key>和memberOf<Key>:(看是不是集合)
    (1) 如果找到所有三个方法,请创建一个响应所有NSSet方法的集合代理对象并返回该对象。随后,此代理对象将NSSet接收到的任何消息转换为,和消息的某种组合countOf<Key>,以创建该对象的对象。实际上,代理对象与与键值编码兼容的对象一起使用,使基础属性的行为就好像它是,即使不是。 enumeratorOf<Key>memberOf<Key>:NSSet
    (2) 没找到,来到第4步

  4. 如果发现收集的访问方法没有简单的存取方法或者组,如果接收器的类方法accessInstanceVariablesDirectly返回YES,搜索名为实例变量_<key>_is<Key><key>,或者is<Key>,按照这个顺序。如果找到,请直接获取实例变量的值,然后继续执行步骤5。否则,请继续执行步骤

  5. 找到对应的属性
    (1) 如果找到是个对象,直接返回;
    (2) 如果该值是支持NSNumber支持的类型,则将其存储在NSNumber实例中并返回它;
    (3) 如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象;

  6. 如果以上都没找到,会调用- (id)valueForUndefinedKey:(NSString *)key,该方法默认引发异常;

设置值

setValue:forKey:根据key` 按照以下步骤查找

  1. 查找第一个名为set<Key>:or的访问器_set<Key>,过访问器完成设置;

  2. 如果没有找到简单的访问,如果类方法accessInstanceVariablesDirectly返回YES,寻找一个实例变量与名称类似_<key>,_is<Key>,<key>,或者is<Key>,按照这个顺序。如果找到,直接用输入值(或展开值)设置变量并完成操作;

3.在找不到访问器或实例变量后,调用setValue:forUndefinedKey:。默认情况下会引发异常,但是的子类可以提供特定行为;

验证属性

validateValue:forKey:error:键值编码协议定义了支持属性验证的方法,可以重写

验证方法认为值对象有效,并在YES不更改值或错误的情况下返回。

验证方法认为值对象无效,但选择不对其进行更改。在这种情况下,该方法返回NO错误参考并将错误参考(如果由调用者提供)设置到一个NSError指示失败原因的对象。

验证方法认为值对象无效,但创建了一个新的有效对象作为替换。在这种情况下,该方法返回,YES而错误对象保持不变。在返回之前,该方法将值引用修改为指向新值对象。进行修改时,即使值对象是可变的,该方法也总是创建一个新对象,而不是修改旧对象。

Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@"%@",error);
}

最后:

键值编码是高效的,尤其是当您依靠默认实现来完成大部分工作时,但是它确实增加了一个间接级别,该级别比直接方法调用要慢一些。因为,KVC需要解析字符串来计算你需要的的答案,此外编译器还无法对它进行错误检查,像键路径,编译器无法判断它是否是错误的路径。
仅当您可以从键值编码中受益时才使用键值编码,或者仅让您的对象参与依赖它的Cocoa技术时,才使用键值编码。

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

推荐阅读更多精彩内容

  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 809评论 0 5
  • 开始 关于键值编码 键值编码是一种机制,通过NSKeyValueCoding非正式协议,对象采用这种机制提供对其属...
    影痕残碎阅读 1,183评论 0 2
  • 源码加翻译 #import <Foundation/NSArray.h> #import <Foundation/...
    CAICAI0阅读 1,146评论 0 50
  • KVC(Key-valuecoding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS...
    榕樹頭阅读 691评论 0 2
  • 国庆假期如愿回到老家,感谢先生不辞辛劳的远道开车带我,感谢大宝的陪伴。 —写在前面 母亲大概率会成为儿女最亲...
    环环lawyer阅读 257评论 0 0