什么是 KVO?
KVO (key-value-observing) 是一种 键值观察
机制, 它允许当前对象去观察目标对象的某个属性的变化; 当被观察对象的属性发生变化后, 会通过特定方法通知观察者对象属性变化的一些情况内容, 观察者对象拿到变化情况后做出相关操作。
关于 KVO 的一些详细介绍可以去 苹果官方文档 了解一下, 这里就不做过多介绍了。
KVO 的初探
进行探索之前, 首先看看 KVO 是怎么使用的:
代码语言:javascript复制 // 方法
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(nullable void *)context;
// 日常使用
[self.person addObserver:self
forKeyPath:@"name"
options:(NSKeyValueObservingOptionNew)
context:NULL];
复制代码
self.person
: 也就是方法调用者, 就是被观察属性name
的对象 (被观察者)observer
: 观察者, 上面例子中的self
options
: 观察的模式, 是个枚举类型, 总共有 4 种观察模式:NSKeyValueObservingOptionNew
、NSKeyValueObservingOptionOld
、NSKeyValueObservingOptionInitial
和NSKeyValueObservingOptionPrior
context
: 在上面的使用中传的是一个NULL
, 因为它的参数类型是void *
是一个指针 (虽然传 nil 也没有问题, 但是严格来说的话应该传NULL
吧)。我们平时好像没有怎么关注过它, 它是用来干什么的呢, 来看看官方文档。
1. context 是什么
首先来看看官方文档里关于 context
的相关介绍:
The context pointer in the addObserver:forKeyPath:options:context: message contains arbitrary data that will be passed back to the observer in the corresponding change notifications. You may specify NULL and rely entirely on the key path string to determine the origin of a change notification, but this approach may cause problems for an object whose superclass is also observing the same key path for different reasons.
A safer and more extensible approach is to use the context to ensure notifications you receive are destined for your observer and not a superclass.
// 大概意思是
// addObserver:forKeyPath:options:context:message中的上下文指针包含相应的更改通知中将要传递回观察者的任意数据。您可以指定NULL,并完全依赖键路径字符串来确定更改通知的来源,但这种方法可能会导致其父类出于不同原因也在观察同样的键路径的情况出现问题。
// 一种更安全、更可扩展的方法是使用上下文来确保您收到的通知是针对您的观察者的,而不是父类的。
复制代码
大概就是在多个观察者的情况下, 有可能不同的类 (上面说的是父类) 拥有相同的 keyPath
, 这样在修改信息回来的时候就会导致无法判断到底是那个被观察对象的属性发生了改变。
通过使用 context
字段, 可以更清楚的辨别当前的通知信息是发送给哪一个 观察者
的。当然, 如果不存在上述注释中说的那种情况下, 使用 NULL
是不会有影响的。
2. 移除观察者
代码语言:javascript复制 // Asking to be removed as an observer if not already registered as one results in an NSRangeException.
// 如果尚未注册为观察员,则请求以观察员身份删除会导致NSRangeException。
复制代码
- 在没有添加过观察者的情况下去调用移除观察者方法会造成程序崩溃, 必须添加过之后才能调用移除方法
// ensuring properly paired and ordered add and remove messages, and that the observer is unregistered before it is freed from memory.
// 应该确保正确配对并按顺序添加和删除消息,并确保在从内存中释放观察者之前将其注销。
复制代码
- 添加观察者和移除观察者必须是 成对出现并且有先后顺序的 , 也就是在不需要使用后必须保证观察者被移除掉, 下面来举个例子说明一下:
// 单例类 Person
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
(instancetype)shareInstance;
@end
// 控制器 A
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
// 控制器 B (A 跳转到 B)
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
复制代码
具体步骤:
- 首先有一个单例
Person
, 在控制器 A
中添加观察者观察name
属性,控制器 A
跳转到控制器 B
控制器 B
也添加观察者观察name
属性, 在B
返回上一级页面 (也就是被销毁的时候) 时应该调用remove
方法将观察者移除掉- 如果
B
在返回的时候没有移除观察者, 在A
再次修改name
属性的时候就会引发崩溃触发野指针异常Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)