KVC和KVO实现监听容器类(数组等)的变化

KVC,即Key-Value Coding,键值编码,简单地说,就是能够由key获取一个object对应的property。举个例子,若是一个对象object,它有一个属性item,你能够经过valueForKey也能够经过object.item来获取它,同时它支持纵调用,即假如object有个属性是个item,item有个属性score,能够经过@“item.score”获取,setValueForKey同理。ios

   CGFloat valueScore,score,valueTotal,total;
    TestObject *object = [[TestObject alloc] init];
    valueScore = [[object valueForKeyPath:@"item.score"] floatValue];
    score = object.item.score;
    valueTotal = [[object valueForKey:@"total"] floatValue];
    total = object.total;

若是咱们定义一个property,系统就已经默认帮咱们实现了KVC,实现原理你们能够能够看看ios的message原理,以前已经介绍。至于它的好处,苹果官方给了一个例子,你们能够感觉一下:数组

Using Key-Value Coding to Simplify Your Code
You can use key-value coding methods in your own code to generalize implementations. For example, in OS X NSTableView and NSOutlineView objects both associate an identifier string with each of their columns. By making this identifier the same as the key for the property you wish to display, you can significantly simplify your code.
Listing 1 shows an implementation of an NSTableView delegate method without using key-value coding. Listing 2 shows an implementation that takes advantage of key-value coding to return the appropriate value using the column identifier as the key.

Listing 1 Implementation of data-source method without key-value coding 
- (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row
{ ChildObject
*child = [childrenArray objectAtIndex:row]; if ([[column identifier] isEqualToString:@"name"]) { return [child name]; } if ([[column identifier] isEqualToString:@"age"]) { return [child age]; } if ([[column identifier] isEqualToString:@"favoriteColor"]) { return [child favoriteColor]; } // And so on. } Listing 2 Implementation of data-source method with key-value coding  - (id)tableView:(NSTableView *)tableview objectValueForTableColumn:(id)column row:(NSInteger)row { ChildObject *child = [childrenArray objectAtIndex:row]; return [child valueForKey:[column identifier]]; }

 

在某种意义上,KVC是为了KVO的实现,下面重点说说KVO。KVO,即Key-Value Observing,也就是一个观察者模式,当一个对象的属性变化会收到对应的变化通知。变化的种类有四种,app

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就是容许本身携带的上下文,在回调里会返回。ide

移除property的监听:函数

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context

有一点必须注意,假如你没有监听这个property而调用移除,会致使系统崩溃。这里须要很是当心。this

而后重写这个回调,当属性改变就会回调这个函数:编码

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

keyPath就是对应的property,context就是监听手动设置的上下文,能够为NULL,字典里能够取到变化的type,原来的值和如今的值。atom

 

而要实现数组(或容器)里面item个数的变化,就要手动重写两个函数支持。下面举个例子:spa

(TestItem有一个数组array,须要实现监听这个数组里内容数据的变化)code

@interface TestItem : NSObject

@property(nonatomic,retain)NSMutableArray *array;

@end

@implementation TestItem


-(void)insertObject:(id)object inArrayAtIndex:(NSUInteger)index //这个是表明property名字,就是上面定义的array,系统会自动生成,要根据本身定义的属性名字改变。

{

    [self.array insertObject:object atIndex:index];

}

-(void)removeObjectFromArrayAtIndex:(NSUInteger)index

{

    [self.array removeObjectAtIndex:index];

}
@end

须要特别主要的是,insertObject和removeObjectFromArrayAtIndex这两个函数都必须同时实现,不然是无效的。

这里只实现了这两个必须实现的函数,剩下的函数看功能须要:

-(void)insertArray:(NSArray *)array atIndexes:(NSIndexSet *)indexes{}

-(void)removeArrayAtIndexes:(NSIndexSet *)indexes{}

-(void)replaceArrayAtIndexes:(NSIndexSet*)indexes withArray:(NSArray *)array{}

-(void)replaceObjectInArrayAtIndex:(NSUInteger)index withObject:(id)object{}

 

固然,有点不方便的是,当咱们要获取这个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便可。

 

最后,KVO能够有依赖属性,便可以实现一个property能够影响其余property的变化而产生kvo的通知:

+(NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

{

    NSSet *set = [superkeyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"firstName"]) {

        NSArray *affectingKeys = @[@"fullName"];

        set = [set setByAddingObjectsFromArray:affectingKeys];

    }

    return set;

}

实现了若是firstName改变,也会产生fullName改变的通知。

 

仅供参考。欢迎指导。

相关文章
相关标签/搜索