你要知道的KVC、KVO、Delegate、Notification都在这里
转载请注明出处 http://blog.youkuaiyun.com/u014205968/article/details/78224820
本系列文章主要通过讲解KVC、KVO、Delegate、Notification的使用方法,来探讨KVO、Delegate、Notification的区别以及相关使用场景,本系列文章将分一下几篇文章进行讲解,读者可按需查阅。
- KVC 使用方法详解及底层实现
- KVO 正确使用姿势进阶及底层实现
- Protocol与Delegate 使用方法详解
- NSNotificationCenter 通知使用方法详解
- KVO、Delegate、Notification 区别及相关使用场景
KVO 正确使用姿势进阶及底层实现
KVO(key value observing)
键值监听是我们在开发中常使用的用于监听特定对象属性值变化的方法,常用于监听数据模型的变化从而可以动态的修改对应视图。能够上述需求的方法有很多,后面要讲的Delegate
和Notification
都可以实现,但都有各自的优缺点和适用场景,需要根据实际情况按需选择,但三者都很重要,在开发中都会使用。
与KVC
相同,OC
在实现KVO
时没有采用实现接口的方式,而是针对NSObject
创建了一个类别,通过这样的方式使得NSObject
的子类可以自行实现NSKeyValueObserving类别
定义的相关方法,其他的如NSArray
、NSSet
这样的集合类也都定义了相关的类别,因此也可以对集合类型进行KVO
的监听。本文主要进行KVO
进阶讲解,基础知识还需读者自行查阅。
学习KVO
最好的方法就是阅读官方文档:Key-Value Observing Programming Guide
KVO基础方法详解进阶
KVO
常用的方法有如下几个:
/*
注册监听器
监听器对象为observer,被监听对象为消息的发送者即方法的调用者在回调函数中会被回传
监听的属性路径为keyPath支持点语法的嵌套
监听类型为options支持按位或来监听多个事件类型
监听上下文context主要用于在多个监听器对象监听相同keyPath时进行区分
添加监听器只会保留监听器对象的地址,不会增加引用,也不会在对象释放后置空,因此需要自己持有监听对象的强引用,该参数也会在回调函数中回传
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
/*
删除监听器
监听器对象为observer,被监听对象为消息的发送者即方法的调用者,应与addObserver方法匹配
监听的属性路径为keyPath,应与addObserver方法的keyPath匹配
监听上下文context,应与addObserver方法的context匹配
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
/*
与上一个方法相同,只是少了context参数
推荐使用上一个方法,该方法由于没有传递context可能会产生异常结果
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
/*
监听器对象的监听回调方法
keyPath即为监听的属性路径
object为被监听的对象
change保存被监听的值产生的变化
context为监听上下文,由add方法回传
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
举一个简单的栗子:
#import <Foundation/Foundation.h>
@interface Account: NSObject
@property (nonatomic, copy) NSString *accountNumber;
@property (nonatomic, assign) double balance;
@end
@implementation Account
@synthesize accountNumber = _accountNumber;
@synthesize balance = _balance;
@end
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, strong) Account *account;
- (void)setObserver;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
//添加监听器
- (void)setObserver
{
/*
监听器对象为Person类的对象本身,被监听的对象为Person类对象持有的account
监听的属性路径为account的balance,可以监听嵌套的对象比如account有一个对象是bank可以监听bank是否营业,可以写"bank.isOpen"
监听上下文设置为nil,相信很多人在使用的时候都会这么写
*/
[self.account addObserver:self forKeyPath:@"balance" options:NSKeyValueObservingOptionNew context:nil];
}
//监听器回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
//判断被监听对象是否为account,并且通过NSString来判断监听属性路径是否一致
if (object == self.account && [keyPath isEqualToString:@"balance"])
{
NSLog(@