iOS KVO的基本使用
iOS - 关于 KVO 的一些总结
1. 什么是 KVO
KVO
的全称是Key-Value Observing
,俗称“键值观察/监听”,是苹果提供的一套事件通知机制,允许一个对象观察/监听另一个对象指定属性值的改变。当被观察对象属性值发生改变时,会触发KVO
的监听方法来通知观察者。KVO
是在MVC
应用程序中的各层之间进行通信的一种特别有用的技术。KVO
和NSNotification
都是iOS
中观察者模式的一种实现。KVO
可以监听单个属性的变化,也可以监听集合对象的变化。监听集合对象变化时,需要通过KVC
的mutableArrayValueForKey:
等可变代理方法获得集合代理对象,并使用代理对象进行操作,当代理对象的内部对象发生改变时,会触发KVO
的监听方法。集合对象包含NSArray
和NSSet
。KVO
和KVC
有着密切的关系,如果想要深入了解KVO
,建议先学习KVC
。
2. KVO 的基本使用
KVO
使用三部曲:添加/注册KVO
监听、实现监听方法以接收属性改变通知、 移除KVO
监听。
- 调用方法
addObserver:forKeyPath:options:context:
给被观察对象添加观察者; - 在观察者类中实现
observeValueForKeyPath:ofObject:change:context:
方法以接收属性改变的通知消息; - 当观察者不需要再监听时,调用
removeObserver:forKeyPath:
方法将观察者移除。需要注意的是,至少需要在观察者销毁之前,调用此方法,否则可能会导致Crash
。
2.1 注册方法
/*
** target: 被观察对象
** observer:观察者对象
** keyPath: 被观察对象的属性的关键路径,不能为nil
** options: 观察的配置选项,包括观察的内容(枚举类型):
NSKeyValueObservingOptionNew:观察新值
NSKeyValueObservingOptionOld:观察旧值
NSKeyValueObservingOptionInitial:观察初始值,如果想在注册观察者后,立即接收一次回调,可以加入该枚举值
NSKeyValueObservingOptionPrior:分别在值改变前后触发方法(即一次修改有两次触发)
** context: 可以传入任意数据(任意类型的对象或者C指针),在监听方法中可以接收到这个数据,是KVO中的一种传值方式
如果传的是一个对象,必须在移除观察之前持有它的强引用,否则在监听方法中访问context就可能导致Crash
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.2 监听方法
如果对象被注册成为观察者,则该对象必须能响应以下监听方法,即该对象所属类中必须实现监听方法。当被观察对象属性发生改变时就会调用监听方法。如果没有实现就会导致Crash
。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
/*
** keyPath:被观察对象的属性的关键路径
** object: 被观察对象
** change: 字典 NSDictionary,属性值更改的详细信息,根据注册方法中options参数传入的枚举来返回
key为 NSKeyValueChangeKey 枚举类型
{
1.NSKeyValueChangeKindKey:存储本次改变的信息(change字典中默认包含这个key)
{
对应枚举类型 NSKeyValueChange
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4,
};
如果是对被观察对象属性(包括集合)进行赋值操作,kind 字段的值为 NSKeyValueChangeSetting
如果被观察的是集合对象,且进行的是(插入、删除、替换)操作,则会根据集合对象的操作方式来设置 kind 字段的值
插入:NSKeyValueChangeInsertion
删除:NSKeyValueChangeRemoval
替换:NSKeyValueChangeReplacement
}
2.NSKeyValueChangeNewKey:存储新值(如果options中传入NSKeyValueObservingOptionNew,change字典中就会包含这个key)
3.NSKeyValueChangeOldKey:存储旧值(如果options中传入NSKeyValueObservingOptionOld,change字典中就会包含这个key)
4.NSKeyValueChangeIndexesKey:如果被观察的是集合对象,且进行的是(插入、删除、替换)操作,则change字典中就会包含这个key,
这个key的value是一个NSIndexSet对象,包含更改关系中的索引
5.NSKeyValueChangeNotificationIsPriorKey:如果options中传入NSKeyValueObservingOptionPrior,则在改变前通知的change字典中会包含这个key。
这个key对应的value是NSNumber包装的YES,我们可以这样来判断是不是在改变前的通知[change[NSKeyValueChangeNotificationIsPriorKey] boolValue] == YES]
}
** context:注册方法中传入的context
*/,>
}
2.3 移除方法
在调用注册方法后,KVO
并不会对观察者进行强引用,所以需要注意观察者的生命周期。至少需要在观察者销毁之前,调用以下方法移除观察者,否则如果在观察者被释放后,再次触发KVO
监听方法就会导致Crash
。
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
2.4 使用示例
以下使用KVO
为person
对象添加观察者为当前viewController
,监听person
对象的name
属性值的改变。当name
值改变时,触发KVO
的监听方法。
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [HTPerson new];
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.name= @"张三";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"object:%@",object);
NSLog(@"change:%@",change);
NSLog(@"context:%@",context);
}
- (void)dealloc
{
[self.person removeObserver:self forKeyPath:@"name"];
}
keyPath:name
object:
change:{ kind = 1; new = "\U70b9\U51fb"; old = ""; }
context:(null)
2.5 实际应用
KVO
主要用来做键值观察操作,想要一个值发生改变后通知另一个对象,则用KVO
实现最为合适。斯坦福大学的iOS
教程中有一个很经典的案例,通过KVO
在Model
和Controller
之间进行通信。如图所示:
2.6 KVO 触发监听方法的方式
KVO
触发分为自动触发和手动触发两种方式。
2.6.1 自动触发
① 如果是监听对象特定属性值的改变,通过以下方式改变属性值会触发KVO
:
- 使用点语法
- 使用
setter
方法 - 使用
KVC
的setValue:forKey:
方法 - 使用
KVC
的setValue:forKeyPath:
方法
② 如果是监听集合对象的改变,需要通过KVC
的mutableArrayValueForKey:
等方法获得代理对象,并使用代理对象进行操作,当代理对象的内部对象发生改变时,会触发KVO
。集合对象包含NSArray
和NSSet
。
2.6.2 手动触发
① 普通对象属性或是成员变量使用:
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
② NSArray
对象使用:
- (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
- (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
③ NSSet
对象使用:
- (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;
- (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key;