最近在看KVC的一些知识,在此记录分享。
1、 valueForKey
valueForKey
是一个取值的操作。关于valueForKey
的调用顺序,如下:
(1)valueForKey
这个方法被调用的时候,首先会调用其相关方法
(a)去找getter方法。
实际上getter方法是一个系列方法,总共包括三个方法(按调用优先级顺序排列如下):
getKey > key > isKey
具体逻辑在代码中看起来更明显:
#pragma mark - getter
// 优先级:第一(最高)
- (NSString *)getName {
return @"getName";
}
// 优先级:第二
- (NSString *)name {
return @"name";
}
// 优先级:第三(几乎不用)
- (NSString *)isName {
return @"isName";
}
(b)如果没有实现上述的三个getter方法,则会调用NSarray(和NSSet)的两个方法。
- (NSInteger)countOfKey
和- (id)objectInKeyAtIndex:(NSInteger)index
- (NSInteger)countOfName {
return 5;
}
- (id)objectInNameAtIndex:(NSInteger)index {
return @"这里的name是key";
}
此时,valueForKey
结果返回的是一个数组。
(2)如果相关方法不存在,会查看+ (BOOL)accessInstanceVariablesDirectly
方法
方法+ (BOOL)accessInstanceVariablesDirectly
的作用为是否允许调用成员变量,结果默认返回YES。
(a)如果这个方法返回值为YES,则找成员变量。
查找的成员变量总共有四个,调用的先后顺序分别为:
_key > _isKey > key > isKey
{
NSString * _name; //优先级最高
NSString * _isName; //优先级第二
NSString * name; //优先级第三
NSString * isName; //优先级最低
}
(b)如果这个方法返回值为NO,则抛出异常。
+ (BOOL)accessInstanceVariablesDirectly
返回NO则无法调用成员变量,运行会崩溃,报错为找不到key。要想防止运行报错,需要重写- (id)valueForUndefinedKey:(NSString *)key
方法。
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"找不到key:%@",key);
return nil;
}
PS:valueForKeyPath
使用场景:现在有一个Dog类,Dog类中有一个@property (nonatomic, assign) int age;
属性,然后还有一个People类,People类中有一个@property (nonatomic, strong) Dog * dog;
属性。当我们想要在外部类中获取People类的一个对象的dog的age值,可以使用valueForKeyPath
替代valueForKey
来简化代码。
Dog类:
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, assign) int age;
@end
#import "Dog.h"
@implementation Dog
- (instancetype)init {
if (self = [super init]) {
_age = 10;
}
return self;
}
@end
People类:
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface People : NSObject
@property (nonatomic, strong) Dog * dog;
@end
#import "People.h"
@implementation People
- (instancetype)init {
if (self = [super init]) {
_dog = [[Dog alloc] init];
}
return self;
}
@end
调用类:
People * p = [[People alloc] init];
/**
* [p valueForKeyPath:@"dog.age"]等价于下面两句
* Dog * dog = [p valueForKey:@"dog"];
* NSString * age = [dog valueForKey:@"age"];
*/
NSString * age = [p valueForKeyPath:@"dog.age"];
2、 setValue: forKey:
setValue: forKey:
是一个设值的操作。关于setValue: forKey:
的调用顺序(可类比取值的操作),如下:
(1)setValue: forKey:
这个方法被调用的时候,首先会调用setter方法
实际上setter方法是一个系列方法,总共包括两个方法(按调用优先级顺序排列如下):
setKey: > setIsKey:
具体逻辑在代码中看起来更明显:
// 优先级高
- (void)setName:(NSString *)name {
NSLog(@"setName:%@", name);
}
// 优先级低
- (void)setIsName:(NSString *)name {
NSLog(@"setIsName:%@", name);
}
(2)如果setter方法不存在,会查看+ (BOOL)accessInstanceVariablesDirectly
方法
在没有实现setter方法的时候,系统会默认去修改成员变量(当然前提是+ (BOOL)accessInstanceVariablesDirectly
返回YES)。虽然成员变量是私有的,但是外界是可以通过KVC修改的。
具体流程同1中(2),详细步骤与讲解请翻上面内容~~
只有一个不同点,就是当+ (BOOL)accessInstanceVariablesDirectly
方法返回NO的时候,要想程序不报错,需要实现- (void)setValue:(id)value forUndefinedKey:(NSString *)key
方法。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"没有找到key:%@", key);
}
PS:setNilValueForKey
基本数据类型设置为nil的问题也会导致项目崩溃。
具体描述:People类有一个@property (nonatomic, assign) int age;
属性,外部创建一个People类的对象p,当给p的age 属性赋值nil的时候,即[p setValue:nil forKey:@"age"];
,运行会报错,报错为找不到setNilValueForKey
。要想防止运行报错,需要重写- (void)setNilValueForKey:(NSString *)key
方法。
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"不能设置nil给key:%@", key);
}
3、自定义KVC
自定义setValue:forKey:
方法,完成系统的功能。
- (void)CustomSetValue:(id)value forKey:(NSString *)key {
if (key == nil || key.length == 0) {
return;
}
// 调用相关setter方法
NSString * setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
[self performSelector:NSSelectorFromString(setKey) withObject:value];
return;
}
NSString * setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
[self performSelector:NSSelectorFromString(setIsKey) withObject:value];
return;
}
// 异常处理方法
if (![self.class accessInstanceVariablesDirectly]) {
NSException * except = [NSException exceptionWithName:@"CustomKVC Exception" reason:@"accessInstanceVariablesDirectly返回NO,无法访问成员变量了" userInfo:nil];
@throw except;
return;
}
unsigned int count = 0;
Ivar * ivarList = class_copyIvarList([self class], &count);
// _key
for (int i = 0; i < count; i++) {
Ivar var = ivarList[i];
NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
object_setIvar(self, var, value);
free(ivarList);
return;
}
}
// _isKey
for (int i = 0; i < count; i++) {
Ivar var = ivarList[i];
NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
object_setIvar(self, var, value);
free(ivarList);
return;
}
}
//key
for (int i = 0; i < count; i++) {
Ivar var = ivarList[i];
NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
if ([keyName isEqualToString:key]) {
object_setIvar(self, var, value);
free(ivarList);
return;
}
}
//isKey
for (int i = 0; i < count; i++) {
Ivar var = ivarList[i];
NSString * keyName = [NSString stringWithUTF8String:ivar_getName(var)];
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
object_setIvar(self, var, value);
free(ivarList);
return;
}
}
//异常处理方法
[self setValue:value forUndefinedKey:key];
free(ivarList);
}
4、KVC容器方法
在用KVO监听p对象的array属性时,直接调用[p.array addObject:@"1"];
是无法监听到array值的变化的,因为没有修改setter方法。如果需要监听到array的值变化,需要使用[[p mutableArrayValueForKey:@"array"] addObject:@"1"];
5、KVC集合运算符
KVC里面共有五个集合运算符:@count @max @min @sum @avg(求平均值)
NSMutableArray * array = NSMutableArray.array;
People * p1 = [[People alloc] init];
p1.age = 10;
[array addObject:p1];
People * p2 = [[People alloc] init];
p2.age = 20;
[array addObject:p2];
People * p3 = [[People alloc] init];
p3.age = 30;
[array addObject:p3];
NSLog(@"count = %@", [array valueForKey:@"@count"]);
NSLog(@"max = %@", [array valueForKeyPath:@"@max.age"]);
输出结果如下:
count = 3
max = 30
网友评论