前言
KVC/KVO在iOS设计模式中具有很重要的地位,在平常的优化代码,面试问答等经常会用到,甚至很多高级的iOS开发技巧都是基于KVC实现的,因此,想提高自己的开发能力,不能仅限于掌握一些KVC/KVO 的基础用法,必须对期底层原理知其所以然,逐步的推敲和掌握,应用到自己的代码中。目前网上关于KVC/KVO的文章在非常多,小编打算站在巨人的肩膀上,加上自己的一些开发经验和理解,遵循由浅到深的原则,通过一系列文章对KVC/KVO 进行整理和归纳,本文先整理了一些KVC/KVO的简单用法。
KVC定义
KVC就是key-value-coding(键值编码),简而言之就是通过key值去操作对象的属性,进行赋值和取值。
KVC最大的优点
通过Key值直接访问对象的属性,进行赋值和取值。而不需要调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性。而不是在编译时确定,可以算得上是iOS开发中的黑魔法之一
KVC最常用的几个函数(我归纳为四大金刚)
setValue:forKey:(为对象的属性赋值)
setValue:forKeyPath:(包含了setValue:forKey:的功能,还可以对对象内的类的属性进行赋值)
valueForKey:(根据key取值)
valueForKeyPath:(根据keyPath取值)
四大金刚使用举例 (生成一个 Person类 和 Car 类)
person.h
@class Car;
@interface Persion : NSObject
{
NSString *_adress;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) Car *car;
@end
Car.h
@interface Car : NSObject
@property (nonatomic, strong) NSNumber *price;
@end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
Persion *persion = [[Persion alloc] init];
[persion setValue:@"zyy1" forKey:@"name"];
[persion setValue:@"zyy2" forKeyPath:@"name"];
NSLog(@"persion.name===%@",persion.name);
Car *car = [[Car alloc] init];
persion.car = car;
[persion setValue:@500 forKeyPath:@"car.price"];
NSLog(@"car.price====%@",[persion valueForKeyPath:@"car.price"] );
[persion setValue:@"北京大望路1" forKeyPath:@"adress"];
[persion setValue:@"北京大望路2" forKeyPath:@"_adress"];
NSLog(@"adress===%@", [persion valueForKey:@"adress"]);
NSLog(@"_adress===%@",[persion valueForKey:@"_adress"]);
}
输出结果
2016-10-27 15:00:01.989 KVC[5749:227850] persion.name===zyy2
2016-10-27 15:00:01.989 KVC[5749:227850] car.price====500
2016-10-27 15:00:01.990 KVC[5749:227850] adress===北京大望路2
2016-10-27 15:00:01.990 KVC[5749:227850] _adress===北京大望路2
通过以上代码执行的结果,我们进行分析比较下面几个问题
KVC中使用forKeyPath:
一个类的成员变量有可能是其他的自定义类,你可以先用KVC获取全部属性,然后再次用KVC来获取这个自定义类的属性,但这样是比较繁琐的,对此,KVC提供了一个解决方案,那就是键路径KeyPath
KVC中 forKey: 和 forKeyPath: 区别
对一个对象的一般属性进行赋值、取值,两个方法是通用,对对象中的对象进的属性行赋值,只有keyPath能够实现
KVC和对象的setter、getter方法的区别
一般情况下,KVC和setter、getter应该说都能达到对对象属性的赋值,并且KVC操作本质也是去调用的setter方法和getter方法,但是对于一些私有属性,那么这个时候setter、getter方法就没有用了,这个时候KVC却可以
KVC中其他几个函数举例
// 默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
/**
在类的内部,进行检查,不符合要求 返回NO ,类外部可以使用
@param ioValue 比对赋值类型
@param inKey 比对属性
@param outError 报错信息
@return 先判断是否符合要求,再使用KVC
*/
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
if ([*ioValue isKindOfClass:[NSString class]] && [inKey isEqualToString:@"name"]) {
return YES;
}
return NO;
}
//如果Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
- (nullable id)valueForUndefinedKey:(NSString *)key {
}
//和上一个方法一样,只不过是设值
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key {
}
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key {
}
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys {
}
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key {
}
//字典转模型
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
注意点:
- 字典转模型的时候,字典中的某一个key一定要在模型中有对应的属性
- 如果一个模型中包含了另外的模型对象,是不能直接转化成功的。
- 通过kvc转化模型中的模型,也是不能直接转化成功的。
上面的这些方法在碰到特殊情况或者有特殊需求还是会用到的,所以可以了解一下.
KVO
KVO也就是key-value-observing(即键值观察),利用一个key来找到某个属性并监听其值得改变。
用法如下:
1、添加观察者
2、在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:
3、移除观察者 -
// 让self去观察persion对象的name属性 (self为观察者、监听者, persion为被观察者、被监听者)
[persion addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
persion.name = @"zyy3";
}
/**
当被监听的对象的属性值发生改变时,观察者会调用该方法
@param keyPath 监听的属性
@param object 监听的对象
@param change 新旧值
@param context 额外参数
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@发生改变",keyPath);
NSLog(@"change==%@",change);
}
// 移除observer
- (void)dealloc {
[persion removeObserver:self forKeyPath:@"name"];
}
以上就是一些 KVC/KVO的一些基础用法。