首先来了解一下什么KVC,KVC就是我们平时所说的键值编码,简单的理解就是可以通过一个key值,修改对象的与这个key对应属性的值,也可以根据该key获取对象的与这个key相对应属性的值。
KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说ObjC中几乎所有的对象都支持KVC操作,常用的KVC操作方法如下:
设置属性的值:
- (void)setValue:(id)value forKey:(NSString *)key;
参数说明:value:想要设置的值, key:键值
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
参数说明:value:想要设置的值, keyPath:键值路径
这个两个方法有什么区别呢?
setValue: forKey:只能修改对象的直接属性,不能修改对象间接属性(对象属性的属性值),setValue: forKeyPath:可以修改间接属性的值,当然也可以修改直接属性的值。
换言之,setValue: forKey:只支持单级映射,而setValue: forKeyPath:支持多级映射。
获取属性的值:
- (id)valueForKey:(NSString *)key; // 只能获取对象的直接属性,
- (id)valueForKeyPath:(NSString *)keyPath; // 可获取对象的间接属性
说明:通过KVC的方式不仅对对象的共有属性(在.h文件声明的属性)有效,对私有属性(在.m文件声明的属性)也有效。
下面通过代码来进一步认识KVC
Dog类
Dog.h
@interface Dog : NSObject
// 公有属性
@property (nonatomic, copy) NSString *name; // 狗的名字
@end</span>
Dog.m
@interface Dog ()
// 私有属性
@property (nonatomic, assign) CGFloat weight;// 狗的重量
@end
Person类
Person.h
@interface Person : NSObject
// 公有属性
@property (nonatomic, copy) NSString *phoneNum; // 人的身高
@property (nonatomic, strong) Dog *dog; // 人有一条狗
@end
Person.m
@interface Person()
// 私有属性
@property (nonatomic, assign) int age; // 人的年龄
@end
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
// 通过KVC方式 修改一个对象的成员属性,
[p setValue:@"10086" forKey:@"phoneNum"];
[p setValue:@(26) forKey:@"age"]; // 私有成员属性
//通过KVC方式 访问一个对象的成员属性
NSString *pPhoneNum = [p valueForKey:@"phoneNum"];
int pAge = [[p valueForKey:@"age"] intValue]; // 私有成员属性
NSLog(@"pPhoneNum = %@, pAge = %d", pPhoneNum, pAge);
// 可以通过KVC修改和访问一个对象的属性的属性 (多级映射)
Dog *d = [[Dog alloc] init];
p.dog = d;
[p setValue:@"HaQiDog" forKeyPath:@"p.dog.name"];
[p setValue:@(10.7) forKeyPath:@"p.dog.weight"]; // 私有成员属性
NSString *dName = [p valueForKeyPath:@"p.dog.name"];
CGFloat dWeight = [[p valueForKeyPath:@"p.dog.weight"] floatValue]; // 私有成员属性
NSLog(@"dName = %@, dWeight = %f", dName, dWeight);
}
return 0;
}
那KVO内部到底是如何实现的呢?
设置属性,优先查找属性的set方法,如果没有该方法则优先考虑搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的setValue:forUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确设置);
读取属性,优先查找属性的get方法,如果没有搜索到则会优先搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的valueforUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确读取);