KVC详解
kvc是什么?
kvc(Key-value-coding)键值编码,一个非正式的协议.提供一种机制间接访问对象的属性.而不是通过调用setter,getter方法获取.就是说在ios开发中,可以允许开发者通过key名直接访问对象的属性,或者给对象的属性赋值.而不需要调用明确存取方法.
kvc有什么作用?
kvc的作用就是可以给对象的私有变量进行赋值.即在运行时动态的访问或修改对象的属性.
kvc的赋值和取值
kvc中定义都是对NSObject的扩展类实现的,oc中的kvc最基本的几个方法:
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来 取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过 KeyPath来设值
NSKeyValueCoding类别中其他的一些方法
- (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
- (BOOL)validateValue:(inout id __nullable * _nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因. - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回. - (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常 - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值. - (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法 - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典.
kvc设值
设值是通过setValue:forKey:的默认实现,给定输入参数value和key。试图在接收调用对象的内部,设置属性名为key的value,通过下面的步骤
1.查找set<Key>、_ set<Key>命名的setter,按照这个顺序,如果找到的话,调用这个方法并将值传进去(根据需要进行对象转换).
2.如果没有发现一个简单的setter,但是accessInstanceVariablesDirectly类属性返回YES,则查找一个命名规则为__<key>、_is<Key>、<key>、is<Key>的实例变量.根据这个顺序,如果发现则将value赋值给实例变量.
3.如果没有发现setter或实例变量,则调用setValue:forUndefinedKey:方法,并默认提出一个异常,但是一个NSObject的子类可以提出合适的行为
eg:
@interface Person : NSObject{
NSString *_name;
}
@end
//创建对象
Person *p = [[Person alloc] init];
//通过kvc赋值
[p setValue:@"小明" forKey:@"name"];
//通过kvc取值
NSLog(@"p的名字是%@",[p valueForKey:@"name"]);
//打印结果
//p的名字是小明
在以上代码中添加,accessInstanceVariablesDirectly为NO,结果如下.
@implementation Person
+(BOOL)accessInstanceVariablesDirectly{
return NO;
}
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"出现异常,此key不存在%@",key);
return nil;
}
-setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"设置值时出现异常,该key不存在%@",key);
}
@end
//打印结果
设置值时出现异常,该key不存在name
出现异常,此key不存在name
p的名字是(null)
如果accessInstanceVariablesDirectly为YES,打印结果和首次显示一样.这里不在重复结果.在修改成员变量名为_name,_isName,name,isName时都会打印出正确的结果.所以简单来说就是*如果没有找到set<key>,会按照_key,_isKey,key,isKey的顺序搜索成员并进行赋值操作.
kvc取值
取值是同valueForKey:来实现的,给定一个key当做输入参数,下面的步骤,在这个接收valueForKey:方法调用的类内部进行操作.
1.通过getter方法搜索实例,例如get<Key>, <key>, is<Key>, _ <key>的拼接方案。按照这个顺序,如果发现符合的方法,就调用对应的方法并拿着结果
2.如果没有找到上面getter方法,kvc会查找countOf<Key>
objectIn<Key>AtIndex、<key>AtIndexes格式的方法.如果这个三个方法倍找到,那么返回一个可以响应NSArray所有方法的代理集合(NSKeyValueArray,是NSArray的子类),给这个代理集合发NSSet的消息,就会以这个几个方法组合的形式调用
3.如果上面的方法名没有找到,那么会同时查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法,如果这三个方式都找到,那么返回一个可以响应NSSet所有的方法的代理集合,给这个代理集合发NSSet的消息,就会以这个几个方法组合的形式调用
4.如果还没有找到,再检查类方法 accessInstanceVariablesDirectly是否为YES,如果是YES,会按_ <key>、_is<key>、<key>、is<key>的顺序搜索成员变量名,如果发现将value赋值给实例变量.如果accessInstanceVariablesDirectly为NO,那么会直接调用forUndefinedKey方法,抛出异常
eg:
@implementation Person
-(NSInteger)getAge{
return 10;
}
@end
//创建对象
Person *p = [[Person alloc] init];
//通过kvc取值
NSLog(@"p的年龄是%@",[p valueForKey:@"age"]);
//打印结果
//p的年龄是10
从上面的代码可以看到,找到getAge方法,就获取到age的值.
分别把getAge方法名改为age、_age、isAge都可以获取到正确的值.上面代码充分说明了kvc在调用valueforkey是搜索key的机制
kvc中keyPath的使用
在开发过程中,一个类的成员变量有可能是自定义类或者其他的复杂数据类型,那么可以使用kvc获取该属性.然后再用kvc来获取这个自定义类的属性.但是这个比较繁琐,所以,kvc提供一个解决方案,那就是键路径keypath
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来
取值 - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过
KeyPath来设值
eg:
@interface Person : NSObject{
NSString *_name;
}
@end
@interface SubPerson : NSObject{
Person *_person;
}
@end
//创建对象Person
Person *p = [[Person alloc] init];
//创建对象SubPerson
SubPerson *subp = [[SubPerson alloc] init];
//通过kvc设置person 值
[subp setValue:p forKey:@"person"];
//通过kvc设值person的给name设值
[subp setValue:@"小花" forKeyPath:@"person.name"];
//通过kvc取值
NSLog(@"SubPerson的person名字是%@",[p valueForKey:@"name"]);
//打印结果
//SubPerson的person名字是小花
从上面例子可以看出我们成功的使用了keypath设置person的name属性值.
kvc异常处理
kvc中最常见的异常就是不小心使用了错误的key,或者设置值不小心传递了nil值.kvc中有专门的方法来处理这些异常
kvc处理nil异常
kvc中通常情况下是不能传递nil的,如果不小心传递了nil,kvc会调用setNilValueForkey方法,这个方法默认是抛出异常.
eg:
@interface Person : NSObject{
NSInteger age;
}
@end
//创建对象Person
Person *p = [[Person alloc] init];
//通过kvc设置age 值
[p setValue:nil forKey:@"age"];
//通过kvc取值
NSLog(@"person的名字是%@",[p valueForKey:@"age"]);
//打印结果
// 不能将age设置为nil
// person的名字是0
kvc处理Undefinekey异常
通常kvc不允许调用一个不存在的key赋值,不然会报错forUndefinekey发生崩溃,所以重写forUndefinekey就可以避免崩溃
eg:
@interface Person : NSObject{
}
@end
//创建对象Person
Person *p = [[Person alloc] init];
//通过kvc设置person 值
[p setValue:nil forKey:@"age"];
//通过kvc取值
NSLog(@"person的名字是%@",[p valueForKey:@"age"]);
//打印结果
// 设置值时出现异常,该key不存在age
// 出现异常,此key不存在age
// person的名字是(null)
KVC使用
KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能,KVC也是许多iOS开发黑魔法的基础。
下面列举iOS开发中KVC的使用场景.
1.动态地取值和设值
利用KVC动态的取值和设值是最基本的用途了.
2.用KVC来访问和修改私有变量
对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的.
3.Model和字典转换
使用kvc和objc的runtime组合可以实现mode和字典的转换(YYModel)
4.访问和修改这些控件的样式
而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了.