1,注册与解除注册 如果我们已经有了包含可供键值观察属性的类,那么就可以通过在该类的对象(被观察对象)上调用名 为 NSKeyValueObserverRegistration 的 category 方法将观察者对象与被观察者对象注册与解除 注册: |
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
这两个方法的定义在 Foundation/NSKeyValueObserving.h 中,NSObject,NSArray,NSSet 均实 |
现了以上方法,因此我们不仅可以观察普通对象,还可以观察数组或结合类对象。在该头文件中,我们还 可以看到 NSObject 还实现了 NSKeyValueObserverNotification 的 category 方法(更多类似方 法,请查看该头文件): |
- (void)willChangeValueForKey:(NSString *)key; - (void)didChangeValueForKey:(NSString *)key;
这两个方法在手动实现键值观察时会用到,暂且不提
值得注意的是:不要忘记解除注册,否则会导致资源泄露。
2,设置属性
将观察者与被观察者注册好之后,就可以对观察者对象的属性进行操作,这些变更操作就会被通知给观察 者对象。注意,只有遵循 KVO 方式来设置属性,观察者对象才会获取通知,也就是说遵循使用属性的 setter 方法,或通过 key-path 来设置:
[target setAge:30];
[target setValue:[NSNumber numberWithInt:30] forKey:@"age"];
3,处理变更通知
观察者需要实现名为 NSKeyValueObserving 的 category 方法来处理收到的变更通知:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object chan ge:(NSDictionary *)change context:(void *)context;
在这里,change 这个字典保存了变更信息,具体是哪些信息取决于注册时 的 NSKeyValueObservingOptions。
键值观察是如何实现的 1,实现机理 键值观察用处很多,Core Binding 背后的实现就有它的身影,那键值观察背后的实现又如何呢?想一想 在上面的自动实现方式中,我们并不需要在被观察对象 Target 中添加额外的代码,就能获得键值观察的 功能,这很好很强大,这是怎么做到的呢?答案就是 Objective C 强大的 runtime 动态能力,下面我 们一起来窥探下其内部实现过程。 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写 基类中任何被观察属性的 setter 方法。 派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设 |
置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。 同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调 用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。 如果你对类和对象的关系不太明白,请阅读《深入浅出 Cocoa 之类与对象》;如果你对如何动态创建类不太 明白,请阅读《深入浅出 Cocoa 之动态创建类》。 苹果官方文档说得很简洁: Key-Value Observing Implementation Details The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance. |