KVO监听属性改变
Key-Value Observing
(简写为KVO
):它的作用就是用来监听类中属性值的变化,实现原理其实就是是观察者模式。被观察者的属性发生改变时,会通知观察者。举个例子当指定对象的属性被修改了,KVO
会自动的去通知相应的观察者。
KVO
的优点:当有属性发生改变时,KVO
会提供自动的消息通知,这样的架构有很多好处。
首先,开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知,这是KVO
机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。开发人员不需要添加任何代码,不需要设计自己的观察者模型,直接可以在工程里使用。
KVO
主要用于用户界面交互,当多个View
共同使用了同一个实体,当这个实体中的某个属性改变时,如果需要更新多个界面,KVO
的作用就发挥出来了。
添加观察者
//通过此方法即可添加对象的观察者
/*
Observer:观察者
KeyPath:观察webView哪个属性
options:NSKeyValueObservingOptionNew:观察新值改变
*/
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context;
例如 监听UIWebView
的canGoBack
属性的变化:
[webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
调用代理方法 只要被观察对象属性有新值就会调用该代理方法
/**
当属性值发生变化的时候,这个方法会被回调
@param keyPath 键值路径
@param object 监听对象
@param change 变化的值
@param context 传递的内容
*/
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
上述代理方法中我们看到有一个特殊的参数:第三个参数:NSDirctionary
类型的。我们会发现他有两个键值对:key是:new和old。他们就是分别代表这个属性值变化的前后值,同时他们的
Value也和之前我们添加监听对象时设置的第三个参数有关:
NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld。那个地方设置了几种状态,这里的
NSDirctionary`中就会有几个键值对
调用dealloc方法,移除观察者
- (void)dealloc{
//移除观察者
[self.webView removeObserver:self forKeyPath:@"canGoBack"];
}
备注
KVO只能检测类中的属性,并且属性名都是通过NSString来查找,编译器不会补全,容易写错
KVC
KVC
是一种使用字符串标识符,间接访问对象属性的机制,它是很多技术的基础。主要的方法就两对方法:
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
其实上面的几个方法使用起来非常的相似,只不过forKeyPath
键值路径的功能更加强大一些.用来访问属性中的属性
,更加推荐使用forKeyPath
键值路径.
既然这里说到了KVC
,我就想聊聊前一阵子在使用setValuesForKeysWithDictionary
遇到的一个问题
setValue:forUndefinedKey
当然我们在做字典转模型的时候一般都是使用MJExtension
来帮助我们完成,其实MJExtension
内部也是运用了Runtime
运行时机制和setValuesForKeysWithDictionary
假设我们这里有一个Person
类,里面只有少的可怜的两个属性:
@property (nonatomic,copy)NSString *name;//姓名
@property (nonatomic,copy)NSString *age;//年龄
然后下面我们模拟一个给Person
赋值的小案例:
NSDictionary *dataSource = @{@"name":@"张三",
@"age":@"18"
};
Person *per=[[Person alloc]init];
// per.name=dataSource[@"name"];
// per.age=dataSource[@"age"];
[per setValuesForKeysWithDictionary:dataSource];
NSLog(@"Person.name=%@",per.name);
NSLog(@"Person.age=%@",per.age);
如果Person
中的属性特别多的时候,一个一个的赋值比较麻烦,所以我们一般都是通过setValuesForKeysWithDictionary
方法来帮助我们实现字典转模型.上面的代码一点问题都没有.但是如果后台给我们的数据多了一个字段的时候,比如:
NSDictionary *dataSource = @{@"name":@"张三",
@"age":@"18",
@"height":@"1.88"
};
Person *per=[[Person alloc]init];
[per setValuesForKeysWithDictionary:dataSource];
系统编译可以通过,但是运行时后崩溃了,下面是崩溃日志:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x60800002d6e0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key height.'
这是因为我们的对象中没有height
这个属性,这时候姐姐我教你一个方法:
#import "Person.h"
@implementation Person
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
@end
我们只需要在对象中实现setValue:forUndefinedKey
这个方法即可,这样就会直接过滤掉给不存在的键值赋值的问题,是不是很简单☺☺
还有一种情况,估计大家也应该经常遇到:
NSDictionary *dataSource = @{@"name":@"张三",
@"age":@"18",
@"height":@"1.88",
@"id":@"101"
};
Person *per=[[Person alloc]init];
[per setValuesForKeysWithDictionary:dataSource];
NSLog(@"Person.ID=%@",per.ID);
因为后台在数据库中的主键经常起名id
,这其实没有任何问题,但是后台人员在给我们返回字段的时候没有经过任何处理就把该字段扔给我们了,要知道的是id
可是苹果的关键字,是不能够当做属性来使用的.
#import "Person.h"
@implementation Person
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if([key isEqualToString: @"id"])
{
self.ID = value;
}
}
@end
setValue:forUndefinedKey:
我们可以使用该方法过滤掉不存在的键值对而防止崩溃,同时在该方法中我们还可以改变因系统的关键字引起的问题等