Jan 25

iOS : KVC与KVO实践

Lrdcq , 2016/01/25 20:01 , 程序 , 閱讀(3383) , Via 本站原創
KVC与KVO两个特性算是在cocoa开发中很基础很基础但是也很高端的特性。它们之所以基础,是因为他们是非常有用的特性,大部分库或者黑科技都有用到。而说他们高端...是因为我们都去用库了,实际代码中...我们基本上还真没用到。那么在实际工程使用中,到底怎么实践?



KVC

一般调用一个对象的属性当然是点语法obj.what来调用了。而作为静态语言,并没有办法像动态语言那样obj["what"]用字符串来调用了——这就是KVC的基本功能,用字符串为key动态调用属性.
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;

在需要通过配置文件或者服务端json或者其他动态信息来配置对象内容的时候,会非常方便。同时,查看api我们看到还有一对看起来更爽的功能,可以对未定义的属性以key进行设置么:
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

可惜,这个方法直接使用的话会报NSUnknownKeyException错误,原来还是要自己写实现代码啊,可惜了。然后,还有一个方法是从字典中设置属性,使用方法类似于:
NSDictionary *dict = @{@"name":@"张三",@"sex":@"男",@"age":@"23"};
[self setValuesForKeysWithDictionary:dict];
  
  
在字典中,我们定义了一堆key-value对的数据,这个方法会将这些数据应用到对象熟悉上。当然,如果有找不到的对象,还是会报错抛出的。还有一对方法:
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

这个就高端了,keyPath是所谓的路径表达式,如foo.bar这样的东西,可以查找设置到属性中的属性,从字典中查找字典,在映射较为复杂的数据结构时将极为方便。同时,往高端上的讲,这是一个叫KVC Collection Operators的特性(https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html),其中包含很多选择器来执行特定的方法。比如常见的对数组集合和字典的操作:

  - @count: 返回一个值为集合中对象总数。
  - @sum: 首先把集合中的每个对象都转换为double类型,然后计算其总和。
  - @avg: 首先把集合中的每个对象都转换为double类型,然后计算其平均值。
  - @max: 使用compare:方法来便利比较出集合中最大的值。
  - @min: 和@max一样,使用compare:方法来便利比较出集合中最小的值。
  
  - @unionOfObjects / @distinctUnionOfObjects: 返回一个由操作符右边的keyPath所指定的对象属性组成的数组。其中@distinctUnionOfObjects会对数组去重, 而@unionOfObjects不会。
  - @distinctUnionOfArrays / @unionOfArrays: 返回了一个数组,同上。
  - @distinctUnionOfSets:返回一个nsset的集合,同上。
  
这些操作可以替代各个筛选器简单的从数组,字典和集合中筛选出需要的数据。更高级的用法参看(http://kickingbear.com/blog/archives/9)。

KVO

kvo的功能描述起来就很简单了,数据监听嘛。一个典型的kvo实践是这样的:(监听self.view.backgroundColor的改变)那么我们:
 [self.view addObserver:self forKeyPath:@"backgroundColor" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
 //然后在self实现方法
 - (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context{    
    //do something
}
//另外在必要的时候
[self.view removeObserver:self forKeyPath:@"backgroundColor"];

这就是kvo最核心的三个方法了。针对对象self.view,我们可以添加/移除针对其某个属性的订阅者,而订阅者只需要默默的实现observeValueForKeyPath方法就可以了。这个方法返回的参数,不但包括监听的对象和key,还包括数据改变情况。在change字典中包含旧数据old,新数据new和传过来的事件类型kind(增加,删除,替换),还有注册时传输进去的上下文信息(一般不需要都传的nil咯)。根据这些信息,我们有足够的信息对监听对象的改变做出处理。

从kvo底层实现来看,kvo是在运行时,当对对象执行setter操作的时候,先后执行willChangeValueForKey与didChangeValueForKey来触发订阅者响应。换句话说,我们可以通过主动调用这两个方法出发订阅者。另外由于是运行时自动添加的方法调用,在self中无论是否通过点语法修改属性都对kvo效果无影响。

kvo的实用之处,在于对于所有类型,如果不是很有必要的话,我们不需要为繁琐的事件设计协议或者调用接口,相对于java等较为严格的语言,为灵活的程序设计带来极大的便利。
logo