Key Value Coding->KVC键值编码

1、介绍:

KVC键值编码在iOS中允许开发者通过 Key 直接访问对象的属性,或者给对象的属性或者成员变量赋值,而不需要调用setter和getter方法。这样就可以在运行时动态在访问和修改对象的属性。
  作为可以称为基类的NSObject有一个分类NSKeyValueCoding, KVC的定义都是对NSObject的扩展来实现的,所以对于所有继承了NSObject在类型,都能使用KVC。
下面就该类中属性方法做分析,同时配合一些例子来展示KVC的功能与实际运用。

2、属性方法分析:

(1.)

@property (class, readonly) BOOL accessInstanceVariablesDirectly;

该属性用来判断是否能直接访问实例变量,而且是readonly属性的,但在实际运用时实际上是重写类方法 + (BOOL)accessInstanceVariablesDirectly; 该类方法默认返回YES,如果设置为NO,则只会对指定的key进行精准搜索(e.g: key 为 @"name",则只会对名字为@"name"的属性进行搜索匹配,不会在搜索@"name"无结果的情况下再去对包含name字符的一些其他属性或成员变量或者方法进行搜索),若没有结果则直接进入异常方法:取值异常方法 --> - (nullable id)valueForUndefinedKey:(NSString *)key;或者 赋值异常方法 --> - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

(2.)

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

直接通过key来取值。苹果源码中对该方法的描述使用了大量的篇幅(这里就不附加英文描述了,太长了~😜),这些描述主要解释了当调用- (nullable id)valueForKey:(NSString *)key; 方法时KVC在内部是按什么样的顺序来寻找key的。
实现查找机制如下:

  • 一、首先按 get<Key>,<key>,is<Key> 的顺序方法查找getter方法,如果找到这样的方法的话会直接调用。同时如果返回值是对象类型则直接返回该对象,如果是BOOL或者int等基础数据类型, 会做NSNumber转换,否则其他的任意类型将被转换成NSValue类型返回(现在不仅限于NSPoint, NRange, NSRect, and NSSize这四个结构体类型);

  • 二、如果通过上面的方法没有找到的话,KVC会查找与-countOf<Key> ,-indexIn<Key>OfObject:,-objectIn<Key>AtIndex:,-<key>AtIndexes: 这些方法名匹配的方法。如果找到-countOf<Key> ,-indexIn<Key>OfObject:这两个方法和剩余其他两个方法中的一个(至少三个同时存在,同时前两个还是必须的!如果这四个同时存在则会优先搜索-objectIn<Key>AtIndex:方法并返回依照这一方法检索的结果,结果是一个包含三个元素的NSOrderedSet对象),那么就会返回一个可以响应NSOrderedSet(有序集合)所有方法的代理集合NSKeyValueOrderedSet,调用这个代理集合的方法,或者说给这个代理集合发送NSOrderedSet的方法,就会以countOf<Key>,-indexIn<Key>OfObject:,objectIn<Key>AtIndex,<Key>AtIndex这几个方法组合的形式调用,消息被发送给原始接收者-valueForKey:。如果还实现了一个可选的方法-get<Ket>:range:方法, 为了最佳的性能该方法将在适当的时候被调用。

  • 三、如果返回有序集合的方法没有找到,KVC会查找与-countOf<Key> ,-objectIn<Key>AtIndex:, -<key>AtIndexes:(对应-[NSArray objectsAtIndexes:]方法) 这些方法名匹配的方法。如果找到-countOf<Key> 这个方法和剩余其他两个方法中的一个,那么就会返回一个可以响应NSArray所有方法的代理集合NSKeyValueArray,调用这个代理集合的方法,或者说给这个代理集合发送NSArray的方法,就会以countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndexs这几个方法组合的形式调用,消息被发送给原始接收者-valueForKey:。如果还实现了一个可选的方法-get<Key>:range:方法, 为了最佳的性能该方法将在适当的时候被调用。* 在这里一定要区别开和第二步顺序的区别!!!*

  • 四、如果返回数组的方法没有找到,KVC会查找与-countOf<Key>,-enumeratorOf<Key>,and -memberOf<Key>:(对应被NSSet类定义的方法) 这些方法名匹配的方法。如果这三个方法全部找到,那么就会返回一个可以响应NSSet所有方法的代理集合NSKeyValueSet,调用这个代理集合的方法,或者说给这个代理集合发送NSSet的方法,就会以-countOf<Key>,-enumeratorOf<Key>,and -memberOf<Key>:这几个方法组合的形式调用,消息被发送给原始接收者-valueForKey:。* 在这里要区别开和第二步顺序及第三步顺序的区别!!!*

  • 五、如果以上getter方法和集合的访问方法均未找到,那么会再检查类方法+ (BOOL)accessInstanceVariablesDirectly;,如果返回YES(默认行为),会按_<key>,_is<Key>,<key>,is<Key>的顺序搜索成员变量名,如果找到这样的实例变量那么返回规则与第一步的返回值顺序是一样的。如果返回是NO,那么会直接调用- (nullable id)valueForUndefinedKey:(NSString *)key;方法。

  • 六、若通过以上方法均为找到,直接调用- (nullable id)valueForUndefinedKey:(NSString *)key;方法。

看了这么多的说明是不是有点懵😖,下面我们用代码来解释这一切~:

步骤一示例:
Person * person = [[Person alloc] init];
NSString * personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
//下图中是Person.m中代码
示例代码-1_1

可以看到搜索到-getName方法有结果后就不会再查找了。

示例代码-1_2

但是下面有一种情况:

示例代码-1_3

可以看到这里打印了null!不是说好的按顺序查找的么?而且下面我也定义了-valueForUndefinedKey:方法(没有在图片中粘出来)同样没有走,这是为什么呢?因为我定义了名字为name的属性!当你定义了属性后,KVC不会去搜索-is<Key>方法!
其实只要有确定的key属性,则只会检索两个方法: -get<Key>,-<key>,如果这两个方法没有结果,则直接放回null。

示例代码-1_4

以下情况全是在没有key属性的情况下进行的操作(再次强调以下:在没有key属性的情况下搜索步骤2,3,4,5,6才有可能发生!!!,否则只会搜索步骤1中的-get<Key>,-<key>这两个方法)

步骤二示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
//下图中是Person.m中代码
示例代码-2_1
示例代码-2_2

由上面两图可以看到:返回一个包含三个元素的NSOrderedSet对象,且元素均相同,其中控制元素个数的是-countOf<Key>方法!!示例代码-2_1中结果元素均为@77,示例代码-2_2中结果只与-<key>AtIndexes:方法返回值的第一个元素有关系!

步骤三示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
示例代码-3_1
示例代码-3_2

这里同步骤二最大的区别就是没有-indexIn<Key>OfObject:方法,若有这一方法绝不会搜索步骤3,4,5的步骤。

步骤四示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
示例代码-4_1

这里必须这三个方法全部存在才可以!

步骤五示例:
Person * person = [[Person alloc] init];
[person setValueForAllMemberVariable];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
示例代码-5_1
示例代码-5_2

这里就不一一的贴图展示效果了,有兴趣的自己可以测试一下😄。

至于步骤六,相信大多数人都遇到过~~

(3.)

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

直接通过key为对象属性或者成员变量赋值。我们直接看KVC在内部是按什么样的顺序来寻找key的然后为对应属性和成员变量赋值的。
实现机制如下:

  • 一、程序会优先调用-set<Key>:属性值方法(setter方法),代码通过setter方法完成设置。这时候如果value参数是 非对象类型 (这再次强调一下** 非对象类型!!**),则不允许将参数设为nil,否则就会调用- (void)setNilValueForKey:(NSString *)key方法,造成崩溃,为了防止出现这种情况可以重写该方法,然后针对出现的nil做一些其他操作。如果value参数是基本数据类型(int,float 等)要先转化为NANumber类型或者NAValue类型。

e.g:

 Person * person = [[Person alloc] init];
 [person setValue:nil forKey:@"name"];
 [person setValue:nil forKey:@"age"];
示例代码-1_1
  • 二、如果没有找到-set<Key>:方法(即不存在属性key),KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行-setValue:forUndefinedKey:方法,当返回为YES时,所以KVC机制会按照 _<key>,_is<Key>, <key>, is<Key> 顺序搜索该类里面有没有这些名称的成员变量,无论该变量是在类接口部分定义,还是在类实现部分定义,也无论用了什么样的访问修饰符,只在存在这样的成员变量都可以对该成员变量赋值。

e.g:

示例代码2-1
示例代码2-2
  • 三、如果以上都没有找到就会调用- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;这一方法。一般重写防止异常。

(4、)

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

这一方法主要是在使用KVC赋值前验证key与Value是否匹配的方法。同时这个方法系统不会自动去检测,需要自己调用去检测。默认返回YES。e.g:

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
    if ([inKey isEqualToString:@"name"]) {
        if ([*ioValue isKindOfClass:[NSString class]]) {
            NSLog(@"yes == %d,ioValueClass == %@", YES, NSStringFromClass([*ioValue class]));
            return YES;
        }else{
            NSLog(@"no == %d,ioValueClass == %@", NO, NSStringFromClass([*ioValue class]));
            return NO;
        }
    }
    return YES;
}
示例代码

(5、)

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

使用该方法获取一个可变集合(NSMutableArray),如果调用该方法则搜索顺序如下:

  • 一、搜索-insertObject:in<Key>AtIndex:, -removeObjectFrom<Key>AtIndex:或者 -insert<Key>AdIndexes ,- remove<Key>AtIndexes 格式的方法。至少有一个insert方法和一个remove方法,那么才会返回一个可以响应NSMutableArray所有方法代理集合(类名是NSKeyValueFastMutableArray2),那么给这个代理集合发送NSMutableArray的方法,会以-insertObject:in<Key>AtIndex: ,-removeObjectFrom<Key>AtIndex:-insert<Key>AdIndexes , -remove<Key>AtIndexes组合的形式调用。除此之外还有两个可选实现的接口:-replaceOnjectAtIndex:withObject: , -replace<Key>AtIndexes:with<Key>:

  • 二、如果没有找到,则搜索-set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set<Key>:方法。 也就是说,-mutableArrayValueForKey:取出的代理集合修改后,会用用-set<Key>: 重新赋值回去。若没有找到,则后面和调用- (void)setValue:(nullable id)value forKey:(NSString *)key;方法的搜索步骤相同。

要特别强调的是:当你单独调用该方法时,是没有任何效果的,必须与 NSMutableArray 的 添加/插入元素方法或者移除元素方法结合使用才会进行搜索~,所以使用该方法就会产生一个问题:这一方法调用既包含了取值搜索也包含了赋值搜索,所以在使用时如果key不是属性或者相关成员变量,或者未开辟内存空间,则必然会抛出异常!

常用场景:KVO监听可变集合类属性时,如果直接操作可变集合(例如[self.xxxArray remove···])时,不会触发KVO,但是通过[self mutableArrayValueForKey:key] remove···]则会触发KVO。
e.g:

调用`mutableArrayValueForKey:`获取一个可变数组,然后插入一条数据
Person类中的方法。第一次调用`-setNameArray:`方法是因为有一个开辟空间的操作`person.nameArray = [NSMutableArray array]; `
与上图作对比,验证步骤二

(6、)

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key ;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

这两个方法与 (5、) 中的运用方式基本相同,只是第一步检索判断的方法不同,所以在此不多赘述。有兴趣的可以看一下官方文档。

(7、)

- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

这一部分方法的应用场景一般是,一个类的成员变量有可能是其他的自定义类,那么通过key的KVC方法一层一层的去获取值或者赋值,就会比较繁琐,keyPath就是解决这问题的。

e.g:

// Person.h
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@end

// Country.h
@interface Country : NSObject
@property (nonatomic,strong) Person *person;
@end
结果验证

使用keyPath要用点 . 去分割不同的key!其实质原理还是key方法的查找顺序!

(8、)

// - valueForKey: 方法查找不到key时会调用,默认抛出NSUndefinedKeyException异常
- (nullable id)valueForUndefinedKey:(NSString *)key;

// -setValue:forKey: 方法查找不到key时会调用,默认抛出NSUndefinedKeyException异常
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

// -setValue:forKey:方法的key为基本类型时传入的value为nil时调用。
- (void)setNilValueForKey:(NSString *)key;

这三个方法其实前面介绍其他方法时都已运用过了,大家可以自己实践一下,印象会更深刻些😆😆😆。

(9、)

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

对于这两个方法相信大家都不会陌生,前者是输入一组key,查找key一一对应的属性value,然后返回key和value组成的字典。主要用于模型转字典!后者是输入一个字典,根据字典的key一一对应类的属性为其赋值。主要用于字典转模型!在这里就不举例说明了~~

2、KVC中的函数操作:

(1、)简单集合运算符:
 共有:@avg平均值)、@sum)、@max最大值)、@min最小值@count对象总数)(只能用在集合对象中(NSArray,NSSet),对象属性必须为基本数据类型,返回值均为NSNumber类型);

验证简单集合运算符功能

(2、)对象运算符
 共有@distinctUnionOfObjects(返回的元素都是唯一的,是去重以后的结果)、@unionOfObjects(返回的是所有的结果--全集)。它们的返回值都是NSArray。

验证对象运算符功能

(3、)数组和集合运算符
@distinctUnionOfArrays@unionOfArrays@distinctUnionOfSets,主要用于集合中包含集合的情况。

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

推荐阅读更多精彩内容