KVO 简介
-
相关文档
Key-Value Observing Programming Guide
Objective-C 的 KVO(二):NSKeyValueObserving.h 代码注释
-
KVO 的概念
KVO(Key-Value Observing),翻译成中文叫:键值观察,是苹果提供的一套事件通知机制,允许观察者监听被观察者给定属性的值的改变。当被观察者给定属性的值发生改变时,会触发观察者的监听方法来通知观察者。KVO 是在 MVC 架构中各层之间进行通信的一种特别有用的技术
KVO 可以监听被观察者中单个属性的改变,也可以监听被观察中集合元素的改变。KVO 监听集合对象的元素的改变时,需要通过 KVC 的集合代理方法获取可变集合代理对象,并使用可变集合代理对象进行操作。当可变集合代理对象内部的元素发生改变时,会触发 KVO 的监听方法。集合对象包括
NSArray、NSOrderedSet、NSSetKVO 和 KVC 有着密切的联系,如果想要深入了解 KVO,建议先学习 KVC
-
KVO 的相关方法
#pragma mark - 相关枚举值与常量的定义 // 被观察者给定属性的观察配置选项,包括观察的内容与发送通知的时机 typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) { NSKeyValueObservingOptionNew = 0x01, // 观察属性的新值,默认在属性改变之后发送通知 NSKeyValueObservingOptionOld = 0x02, // 观察属性的旧值,默认在属性改变之后发送通知 NSKeyValueObservingOptionInitial = 0x04, // 在添加观察者时,立即发送一次通知。在每次属性改变之后,也会发送一次通知 // 如果需要观察属性的新值,则配合 NSKeyValueObservingOptionNew 使用 // 如果需要观察属性的旧值,则配合 NSKeyValueObservingOptionOld 使用 NSKeyValueObservingOptionPrior = 0x08 // 在属性改变之前和属性改变之后,各发一次通知 // 在属性改变之前发送的通知中,始终包含 NSKeyValueChangeNotificationIsPriorKey 条目,始终不包含 NSKeyValueChangeNewKey 条目 // 如果需要观察属性的新值,则配合 NSKeyValueObservingOptionNew 使用。属性的新值只包含在属性改变之后的通知中 // 如果需要观察属性的旧值,则配合 NSKeyValueObservingOptionOld 使用。属性的旧值既会包含在属性改变之前的通知中,也会包含在属性改变之后的通知中 }; // 被观察者给定属性的改变类型(即 change 字典中 NSKeyValueChangeKindKey 条目的可能值) typedef NS_ENUM(NSUInteger, NSKeyValueChange) { NSKeyValueChangeSetting = 1, // 对属性进行了设值操作(用于非集合类型和集合类型) NSKeyValueChangeInsertion = 2, // 对属性进行了插入操作(仅用于集合类型) NSKeyValueChangeRemoval = 3, // 对属性进行了移除操作(仅用于集合类型) NSKeyValueChangeReplacement = 4, // 对属性进行了替换操作(仅用于集合类型) }; // 被观察者给定集合属性的事件类型 typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) { NSKeyValueUnionSetMutation = 1, // 对应于属性的改变类型 NSKeyValueChangeInsertion, 对应于调用方法 -[NSMutableSet unionSet] NSKeyValueMinusSetMutation = 2, // 对应于属性的改变类型 NSKeyValueChangeRemoval, 对应于调用方法 -[NSMutableSet minusSet] NSKeyValueIntersectSetMutation = 3, // 对应于属性的改变类型 NSKeyValueChangeRemoval, 对应于调用方法 -[NSMutableSet intersectSet] NSKeyValueSetSetMutation = 4 // 对应于属性的改变类型 NSKeyValueChangeReplacement, 对应于调用方法 -[NSMutableSet setSet] }; // change 字典中所包含的键 typedef NSString * NSKeyValueChangeKey NS_STRING_ENUM; // 将 NSString* 取别名为 NSKeyValueChangeKey,即 change 字典中键的类型为 NSString* FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey; // 用于标识被观察者给定属性的改变类型,对应字符串 @"kind" FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey; // 用于标识被观察者给定属性的新值,对应字符串 @"new" FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey; // 用于标识被观察者给定属性的旧值,对应字符串 @"old" FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey; // 用于标识被观察者给定集合属性被更改的索引,对应字符串 @"indexes" FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey; // 用于标识是否为给定属性改变之前所发送的通知,对应字符串 @"notificationIsPrior" #pragma mark - KVO 使用 3 步曲 // 注册观察者 // @param.observer 观察者 // @param.keyPath 被观察者给定属性的关键路径 // @param.options 观察的配置选项,用于确定观察者收到的通知中所包含的内容,以及通知的发送时间 // @param.context 用于在通知触发时传递给观察者的上下文,在观察者的监听方法中可以接收到这个数据,是 KVO 中的一种传值方式 // 可以传入任意类型的 Objective-C 对象或者 C 指针。如果传入的是一个 Objective-C 对象,则必须在移除观察者之前持有它的强引用,否则在观察者的监听方法中访问 context 就可能导致程序 Crash // @note 方法的调用者,即为被观察者 -(void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; // 通知触发时的回调 // @param.keyPath 被观察者给定属性的关键路径 // @param.object 被观察者 // @param.change 包含被观察者给定属性详细的更改信息 // @param.context 在注册观察者时传递的上下文 // @note 观察者需要实现此回调以监听被观察者中给定属性的值的改变 -(void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context; // 注销观察者 // @param.observer 观察者 // @param.keyPath 被观察者给定属性的关键路径 // @param.context 在注册观察者时传递的上下文 // @note 方法的调用者,即为被观察者 -(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context; -(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; #pragma mark - KVO 与 NSArray // 注册观察者 // @param.observer 观察者 // @param.indexes 数组中被观察的元素的索引 // @param.keyPath 数组元素被观察属性的关键路径 // @param.options 观察的配置选项,用于确定观察者收到的通知中所包含的内容,以及通知的发送时间 // @param.context 用于在通知触发时传递给观察者的上下文,在观察者的监听方法中可以接收到这个数据,是 KVO 中的一种传值方式 // 可以传入任意类型的 Objective-C 对象或者 C 指针。如果传入的是一个 Objective-C 对象,则必须在移除观察者之前持有它的强引用,否则在观察者的监听方法中访问 context 就可能导致程序 Crash // @note 在此方法中,被观察者不是方法的调用者(NSArray、NSMutableArray) // 在此方法中,被观察者是数组中的元素 -(void)addObserver:(NSObject *)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; // 注销观察者 // @param.observer 观察者 // @param.indexes 数组中要注销观察者的元素的索引 // @param.keyPath 要注销观察者的数组元素给定属性的关键路径 // @param.context 在注册观察者时传递的上下文 // @note 在此方法中,要注销观察者的不是方法的调用者(NSArray、NSMutableArray) // 在此方法中,要注销观察者的是数组中的元素 -(void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath context:(nullable void *)context; -(void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath; #pragma mark - 手动触发 KVO // 用于手动触发非集合类型的被观察者给定属性的 KVO 通知,以下两个方法必须始终成对地的调用 // @param.key 被观察者中需要手动触发 KVO 通知的属性的关键路径 -(void)willChangeValueForKey:(NSString *)key; -(void)didChangeValueForKey:(NSString *)key; // 用于手动触发有序集合类型的被观察者给定属性的 KVO 通知,以下两个方法必须始终成对地的调用 // @param.changeKind 集合元素的改变类型(插入、移除、替换) // @param.indexes 需要手动触发 KVO 通知的集合元素的索引 // @param.key 集合元素中需要手动触发 KVO 通知的属性的关键路径 -(void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key; -(void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key; // 用于手动触发无序集合类型的被观察者给定属性的 KVO 通知,以下两个方法必须始终成对地的调用 // @param.key 集合元素中需要手动触发 KVO 通知的属性的关键路径 // @param.mutationKind 集合事件的类型(union、minus、intersect、set) // @param.objects 用于集合事件的集合元素 -(void)willChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects; -(void)didChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects; #pragma mark - KVO 的触发控制:自动触发 or 手动触发 // 用于控制被观察者中给定 key 所标识的属性的触发模式 // @param.key 需要控制触发模式的属性的关键路径 // @return YES - 自动触发,NO - 手动触发 // @note 此方法的默认实现会在被观察者所属的类中搜索名称为 +automaticallyNotifiesObserversOf<Key> 的方法 // 如果找到,则返回 +automaticallyNotifiesObserversOf<Key> 的调用结果 // 如果找不到,则返回 YES +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key; #pragma mark - KVO 的属性依赖 // 用于控制被观察者中给定 key 所标识的属性依赖的其他属性 // @param.key 用于标识依赖属性的关键路径 // @return 用于标识被依赖属性的关键路径的集合 // @note 此方法的默认实现会在被观察者所属的类中搜索名称为 +keyPathsForValuesAffecting<Key> 的方法 // 如果找到,则返回 +keyPathsForValuesAffecting<Key> 的调用结果 // 如果找不到,为了向后的二进制兼容性,则返回根据之前已弃用的 +setKeys:triggerChangeNotificationsForDependentKey: 的调用所提供的信息计算出的关键路径的集合 +(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key; #pragma mark - 被观察者中全部的观察信息 // 包括每个观察者的 observer、keyPath、options、context @property (nullable) void *observationInfo NS_RETURNS_INNER_POINTER;
KVO 的基本使用
- KVO 使用三步曲
-
被观察者添加(注册)观察者,即
被观察者调用-addObserver:forKeyPath:options:context:方法为自己添加观察者 -
观察者实现监听方法以接收被观察者给定属性改变的通知,即
在观察者所属的类中实现-observeValueForKeyPath:ofObject:change:context:方法以接收被观察者给定属性改变的通知
如果一个对象被注册为观察者,则该对象必须能响应此回调方法,即该对象所属类中必须实现此回调方法。当被观察者给定属性发生改变时就会调用此回调方法,没有实现会导致程序 Crash -
被观察者移除(注销)观察者,即
被观察者调用-removeObserver:forKeyPath:context:方法移除观察者。因为被观察者在调用 KVO 注册方法添加观察者之后,并不会对观察者进行强引用,所以需要注意观察者的生命周期,被观察者需要在观察者被销毁之前调用此方法以移除观察者,否则当被观察者给定属性的值再次改变时,KVO 向已释放的观察者再次发送属性值改变的通知,可能会导致程序 Crash代码示例:

// ViewController.m #import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic, strong) Person* aPerson; @end @implementation ViewController // 懒加载 -(Person *)aPerson { if (!_aPerson) { _aPerson = [[Person alloc] init]; } return _aPerson; } -(void)viewDidLoad { [super viewDidLoad]; // 1.被观察者添加(注册)观察者 [self.aPerson addObserver:self forKeyPath:

本文深入讲解了Objective-C中的Key-Value Observing (KVO)原理、使用方法、集合操作、属性依赖及底层实现,还包括自定义KVO的实现示例。通过实例演示,助您轻松理解和应用KVO技术。
最低0.47元/天 解锁文章
4033

被折叠的 条评论
为什么被折叠?



