KVC定义
KVC:即key-value-coding,键值编码。
KVC的使用
-
通过键值路径可为对象的属性进行赋值,也可以设置对象的私有属性。
Person *person = [Person alloc] init]; [person setValue:@"yomi" forKey:@"name"]; // 如果对象的某一属性也是对象 [person setValue:@1 forKeyPath:@"_id"];
-
通过键值路径获取对象某一属性的值,也可以获取私有属性。
NSString *name = [person valueForKey:@"name"]; // 也可以通过kvc获的_id NSNumber *p_i = [person valueForKeyPath:@"_id"];
-
通过KVC获取集合中的元素个数、最大值、最小值、平均值以及求和。⚠️注意:通过KVC来获取集合中元素的个数时应当使用
@"@count"
Person *p1 = [[Person alloc] init]; p1.age = 30; Person *p2 = [[Person alloc] init]; p2.age = 50; Person *p3 = [[Person alloc] init]; p3.age = 80; NSArray *arr = [NSArray arrayWithObjects: p1, p2, p3, nil]; NSLog(@"\narr.count:%@\nmaxAge:%@\nminAg:%@\navgAge:%@\nsumAge:%@", [arr valueForKey:@"@count"], [arr valueForKeyPath:@"@max.age"], [arr valueForKeyPath:@"@min.age"], [arr valueForKeyPath:@"@avg.age"], [arr valueForKeyPath:@"@sum.age"]);
-
通过kvc赋值模型
NSDictionary *dict = @{@"id":@(10), @"name":@"ver", @"age":@90}; Person *person = [[Person alloc] init]; [person setValuesForKeysWithDictionary:dict];
-
完整测试代码如下:
// // ViewController.m // kvoTest // // Created by Admin on 2021/12/7. // #import "ViewController.h" #import <objc/message.h> @interface Person:NSObject { NSNumber *_id; } @property (nonatomic,assign) int age; @property (nonatomic,copy) NSString *name; @property (nonatomic,assign) int p_id; @end @implementation Person + (instancetype)personWithDict:(NSDictionary *)dict { Person *person = [[Person alloc] init]; [person setValuesForKeysWithDictionary:dict]; return person; } - (void)print{ NSLog(@"id:%@",_id); } // Person类中找不到key(此时找不到id)对应的属性时,调用此方法 - (void)setValue:(id)value forUndefinedKey:(NSString *)key { if ([key isEqualToString:@"id"]) { self.p_id = [value intValue]; } else { NSLog(@"Person找不到%@对应的属性", key); } } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; [p setValue:@"ddd" forKeyPath:@"name"]; NSLog(@"name:%@",p.name); p.age = 40; [p setValue:@1 forKeyPath:@"_id"]; [p print]; Person *p1 = [[Person alloc] init]; p1.age = 30; Person *p2 = [[Person alloc] init]; p2.age = 50; Person *p3 = [[Person alloc] init]; p3.age = 80; NSArray *arr = [NSArray arrayWithObjects: p, p1, p2, p3, nil]; NSLog(@"\narr.count:%@\nmaxAge:%@\nminAg:%@\navgAge:%@\nsumAge:%@", [arr valueForKey:@"@count"], [arr valueForKeyPath:@"@max.age"], [arr valueForKeyPath:@"@min.age"], [arr valueForKeyPath:@"@avg.age"], [arr valueForKeyPath:@"@sum.age"]); NSDictionary *dict = @{@"id":@(10), @"name":@"ver", @"age":@90}; Person *p5 = [Person personWithDict:dict]; NSLog(@"name:%@,id:%d,age:%d",p5.name,p5.p_id,p5.age); } @end
KVC的原理
<aside>
💡 OC中一个属性对应有四个成员变量,并且他们的优先级依次为:_key
>_isKey
>key
>isKey
</aside>
KVC设值原理为,当调用setValue:属性值 forKey:@”name“
的代码时,底层的执行机制如下:
- 程序优先调用set<Key>:属性值方法,代码通过setter方法完成设置。注意,这里的<key>是指成员变量名,首字母大小写要符合KVC的命名规则,下同
- 如果没有找到
setName:
方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly
方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:
方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为_<key>
的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以_<key>
命名的变量,KVC都可以对该成员变量赋值 - 如果该类即没有
set<key>:
方法,也没有_<key>
成员变量,KVC机制会搜索_is<Key>
的成员变量 - 和上面一样,如果该类即没有
set<Key>:
方法,也没有_<key>
和_is<Key>
成员变量,KVC机制再会继续搜索<key>
和is<Key>
的成员变量。再给它们赋值 - 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的
setValue:forUndefinedKey:
方法,默认是抛出异常
KVC取值原理为,当调用valueForKey:@”name“
的代码时,KVC对key
的搜索方式不同于setValue:属性值 forKey:@”name“
,其搜索方式如下:
- 首先按
get<Key>
,<key>
,is<Key>
的顺序方法查找getter
方法,找到的话会直接调用。如果是BOOL
或者Int
等值类型, 会将其包装成一个NSNumber
对象 - 如果上面的
getter
没有找到,KVC则会查找countOf<Key>
,objectIn<Key>AtIndex
或<Key>AtIndexes
格式的方法。如果countOf<Key>
方法和另外两个方法中的一个被找到,那么就会返回一个可以响应NSArray
所有方法的代理集合(它是NSKeyValueArray
,是NSArray
的子类),调用这个代理集合的方法,或者说给这个代理集合发送属于NSArray
的方法,就会以countOf<Key>
,objectIn<Key>AtIndex
或<Key>AtIndexes
这几个方法组合的形式调用。还有一个可选的get<Key>:range:
方法。所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名 - 如果上面的方法没有找到,那么会同时查找
countOf<Key>
,enumeratorOf<Key>
,memberOf<Key>
格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet
所的方法的代理集合,和上面一样,给这个代理集合发NSSet
的消息,就会以countOf<Key>
,enumeratorOf<Key>
,memberOf<Key>
组合的形式调用 - 如果还没有找到,再检查类方法
+ (BOOL)accessInstanceVariablesDirectly
,如果返回YES(默认行为),那么和先前的设值一样,会按_<key>,_is<Key>,<key>,is<Key>
的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly
返回NO的话,那么会直接调用valueForUndefinedKey:
- 还没有找到的话,调用
valueForUndefinedKey: