观察容器类通常会造成一些困扰。要牢记一点是观察容器类和观察其中的对象是不同的。
如果一个容器包含了Adam Bob和Carol,那么增加Denise就改变了容器。而改变Adam不会改变容器。
如果要观察容器中的对象的改变,必须观察那些对象,而不是容器本身。
一般来讲,可以先观察容器,在添加对象后观察对象,而在删除对象后停止观察对象。
在某种意义上,KVC是为了KVO的实现,下面重点说说KVO。KVO,即Key-Value Observing,也就是一个观察者模式,当一个对象的属性变化会收到对应的变化通知。变化的种类有四种,
typedef NS_OPTIONS(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4
};
其中NSKeyValueChangeSetting主要是对于一个对象地址的变化(或常量的变化),而后三个主要是对容器类的改变,举个例子,就是一个数组里新加一个item或者删掉一个item都会收到对应的通知,这就是kvo的强大之处了。
主要实现代码:监听property:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
keyPath就是对应的property的string,context就是允许自己携带的上下文,在回调里会返回。
移除property的监听:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
有一点必须注意,假如你没有监听这个property而调用移除,会导致系统崩溃。这里需要非常小心。
然后重写这个回调,当属性改变就会回调这个函数:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
keyPath就是对应的property,context就是监听手动设置的上下文,可以为NULL,字典里可以取到变化的type,原来的值和现在的值。
而要实现数组(或容器)里面item个数的变化,当然,有点不方便的是,当我们要获取这个TestItem的array时,不能简单用item.array,而必须调用
[[item mutableArrayValueForKey:@"array"] addObject:item];
只有用mutableArrayValueForKey得到的数组变化才会收到通知。
我们可以看看这样变化产生的change:
(lldb) po change$6 = 0x09169bc0 {
indexes = "<NSIndexSet: 0x9169a10>[number of indexes: 1 (in 1 ranges), indexes: (0)]";
kind = 2;
new = ( "<TestItem: 0x7163cf0>" );
}
indexes描述了变化的index,kind = 2即上面所说的NSKeyValueChangeInsertion,代表数组新插入了一个数据,new就是一个增加的数据数组。其他两种类型的变化大家可以感受一下。
至于NSmutableDictionary的实现原理一样,就是把array换成dictionary即可。