细说KVO & KVC & NSNotifica
<p style = "text-indent:2em;font-size = 20px;">在iOS开发过程中,我们经常会听到或者用到KVO,KVC,NSNotificationCenter等,但是很多时候,我们可能没有那么了解,下面让我们来详细了解下他们的概念、用法以及他们之间的关系吧~</p>
本篇博客共分以下几个模块来介绍:
- 什么是KVC?
- 什么是KVO?
- KVC与KVO的关系
- KVC Collection Operators
- 什么是NSNotificationCenter?
- NSNotificationCenter与KVO的比较
- NSNotificationCenter与Delegate的比较
欢迎访问作者个人博客www.dullgrass.com,更多好的文章与您分享
下面就进入正题吧~
什么是KVC?
1. KVC(键值编码)的概念:
<p style = "text-indent:2em;font-size = 20px;">苹果的官方文档描述: KVC(Key-Value-Coding)是一种通过字符串描述而不是通过调用访问方法或者直接使用实例变量的非直接的访问对象属性的机制。</p>
<p style = "text-indent:2em;font-size = 20px;">KVC 是观察者模式在Objective-C中的实现之一,它以分类(非正式协议)的形式被定义在NSObject中,从协议的角度看,是定义了一套让开发者遵守的规范和使用方法。</p>
2. KVC的用途
<p style = "text-indent:2em;font-size = 20px;">在Cocoa的MVC框架中,KVC是ViewControler和Model沟通的桥梁。</p>
<p style = "text-indent:2em;font-size = 20px;">KVC的基本方法都定义在了NSKeyValueCoding的非正式协议中,如下图所示:(NSObject默认实现了该协议,也就是说OC中几乎所有的对象都支持KVC操作)。</p>
@interface NSObject(NSKeyValueCoding)
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
<p style = "text-indent:2em;font-size = 20px;">KVC可以用来访问对象的属性、一对一的关系对象、一对多的关系对象</p>
- 访问对象属性:也可以是对象的成员变量,成员变量是私有的也可以访问,属性可以是对象,也可以是数值类型和结构体,非对象类型的参数和返回值会自动封装成NSValue活着NSNumber类型。
valueForKey:会返回跟接收者相关的key的值,如果对于指定的key没有访问器或者实例变量,则给自己发送一个valueForUndefineKey:消息,这个方法的默认实现是抛出一个NSUndefinedKeyException - 通过关系访问对象:假设对象person有属性address,属性address有属性city,我们可以通过person来访问city:
[person valueForKeyPath:@"address.city"];
valueForKeyPath:返回跟接收者相关的键路径的值,对于子系列中任何不遵循KVC的对象,都会收到一个valueForUndefineKey:消息。
-
访问集合对象: 可以是可变集合和不可变集合,可变集合与KVO结合,可以实现批量更新(需传入多个对象)的功能。
dictionaryWithValuesForKeys:会检索数组中所有跟接收者相关的key的值,返回的NSDictionary中包含了数组中所有key的值。
<p style = "text-indent:2em;font-size = 20px;">KVC可以设置属性的值</p>
setValue:forKey用来将接收者中相关key的值设置成指定的值。在这个方法的实现中,会将NSValue的值转换成普通的数值然后赋值给属性。
<p style = "text-indent:2em;font-size = 20px;">KVV(Key-Value-Validate)键值验证</p>
- (BOOL)validateValue:(inout id *)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
什么是KVO?
-
观察者模式的基本思想:
<p style = "text-indent:2em;font-size = 20px;">观察者模式主要是,通过一个对象来管理所有依赖于它的观察者对象,并在它自身的状态改变时主动通知观察者对象。 目标对象通知观察者通常是通过调用各观察对象所提供的接口方法来实现的.观察者模式比较完美的将目标对象与观察者对象解耦.</p> -
KVO 的应用场景:
<p style = "text-indent:2em;font-size = 20px;">当一个对象的特定属性改变的时候,需要被通知一个或者多个对象的时候。</p> -
KVO 的使用流程:
- 注册与解除注册
keyPath不可以为nil
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
- 设置Value
[target setAge:30]; //setter
[target setValue:[NSNumber numberWithInt:30] forKey:@"age"]; //setValue:forKey
- 处理变更通知
<p style = "text-indent:2em;font-size = 20px;">观察者需要实现名为 NSKeyValueObserving 的 category 方法来处理收到的变更通知</p>
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
<p style = "text-indent:2em;font-size = 20px;">当一个观察者需要观察多个对象的同一个keyPath时,可以通过设置context来区分不同的通知。</p>
- 属性依赖
<p style = "text-indent:2em;font-size = 20px;">简单点说,就是通过观察一个key的值,来观察多个属性的变化,需要对象实现以下几个方法。</p>
+ (NSSet *)keyPathsForValuesAffectingAddress
{
NSSet *set = [NSSet setWithObjects:@"firstName", @"lastName", nil];
return set; //这方法使得 Key 之间能够建立依赖关系
}
//设置属性之间的依赖关系
- (NSString *)address
{
return [NSString stringWithFormat:@"%@%@",self.province,self.street];
}
KVC与KVO的关系
<p style = "text-indent:2em;font-size = 20px;">KVO是基于KVC实现的,只有我们调用KVC去访问key值的时候KVO才会起作用。</p>
KVC Collection Operators(KVC集合操作符)
-
KVC 集合操作符的概念:
<p style = "text-indent:2em;font-size = 20px;">KVC集合操作符允许在valueForKeyPath:方法中使用key path符号“@”,在一个集合中执行方法。结果可以被返回或者链接。</p> -
KVC 集合操作符的类型(根据返回值的类型来分):
- 简单的集合操作符:返回值是NSString、NSNumber或者NSDate
- 对象操算符:返回值是一个数组
- 数组和集合操作符:返回值是一个数组或者集合
- 简单的集合操作符
- @count:返回一个值为集合中对象总数的NSNumber对象
- @sum: 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象
- @avg: 首先把集合中的每个对象都转换为double类型,然后计算其平均值,最后返回一个值为这个总和的NSNumber对象
- @max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较
- @min: 和@max一样,但是返回的是集合中的最小值。
- 使用示例:
products是数组,数组中存放了很多对象,每个对象都有一个price的属性。
[products valueForKeyPath:@"@sum.price"];
可以用self作为操作符后面的keyPath来获取一个由NSNumber组成的数组或者集合的总值。
[@[@1,@2] valueForKey:@"@max.self"];
- 对象操作符
- @distinctUnionOfObjects:获取数组中每个对象的属性的值,放到一个数组中并返回,会对数组去重。
- @unionOfObjects: 同@distinctUnionOfObjects,但是不去重。
- 使用示例:
Person *lilei = [[Person alloc] init];
lilei.name = @"LiLei";
Person *hanMeiMei = [[Person alloc] init];
hanMeiMei.name = @"hanMeiMei";
NSArray *array = @[lilei, hanMeiMei];
NSLog(@"array is %@",[array valueForKeyPath:@"@distinctUnionOfObjects.name"]);
输出结果为:
2015-11-10 16:08:07.977 TestPro[49746:521302] array is (
LiLei,
hanMeiMei
)
- 数组和集合操作符
- @distinctUnionOfArrays: 获取数组中每个数组中的每个对象的属性的值,放到一个数组中并返回,会对数组去重复。
- @unionOfArrays:同@distinctUnionOfArrays,但是不去重。
- @distinctUnionOfSets: 获取集合中每个集合中的每个对象的属性的值,放到一个集合中并返回。
- 使用示例:
Person *lilei = [[Person alloc] init];
lilei.name = @"LiLei";
Person *hanMeiMei = [[Person alloc] init];
hanMeiMei.name = @"hanMeiMei";
NSArray *array = @[lilei, hanMeiMei];
NSLog(@"array is %@",[ @[array,array] valueForKeyPath:@"@unionOfArrays.name"]);
输出结果为:
2015-11-10 16:51:26.137 TestPro[50404:556930] array is (
LiLei,
hanMeiMei,
LiLei,
hanMeiMei
)
什么是NSNotificationCenter?
- 通知中心的概念:
<p style = "text-indent:2em;font-size = 20px;">通知中心是 Foundation 框架的一个子系统,也是一个观察者模式,它向应用程序中注册为某个事件观察者的所有对象广播消息(即通知),可以在不同类之间通信的时候使用。</p>
- 注意当接受到消息后,不想再收到消息了,要把observer删除remove。一般在类销毁的时候remove。
- 组成通知中心的两个类:
- NSNotificationCenter:获取NSNotificationCenter的方法只有一种,即[NSNotificationCenter defaultCenter],并且NSNotificationCenter是一个单例模式,一旦创建,这个通知中心的对象会一直存在于一个应用的生命周期。
- NSNotification: 这是消息携带的载体,通过它,可以把消息内容传递给观察者。
- 通知中心的使用流程:
- 注册通知:NSNotificationCenter 注册观察者对某个事件(以字符串命名)感兴趣,及该事件触发时该执行的 Selector 或 Block.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationMethod:) name:@"notificationName" object:nil];
参数注释:
第一个参数(self):负责监听的对象
第二个参数(@selector(notificationMethod:)):接收到通知之后,监听对象需要执行的方法。
第三个参数(@"notificationName"):通知的名称,也是通知的唯一标示,编译器就是通过这个找到通知的。
第四个参数(nil):最后一个参数是表示会对哪个发送者对象发出的事件作出响应,nil 时表示接受所有发送者的事件。
- 发送通知:手动发送消息
[[NSNotificationCenter defaultCenter] postNotificationName:@"notificationName" object:test userInfo:@{@"key":@"value"}];
参数注释:
第一个参数(@"notificationName"):通知的名称,这个名称必须和后面接收通知的名称一致
第二个参数(test):可以传递的一个参数对象
第三个参数(@{@"key":@"value"}):可以传递的,与通知相关的信息,
- 移除通知:在观察者不再接收消息的时候,移除通知
- (void)dealloc
{
//移除指定的通知,不然会造成内存泄露
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"happyValueNotification" object:nil];
//移除所有的通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
NSNotificationCenter与KVO的比较
- 相同点:
- NSNotificationCenter与KVO的实现原理都是观察者模式。用于监听操作
- 不同点:
- KVO只用来监听属性值的变化,这个发送监听的操作是系统控制的,我们控制不了,我们只能控制监听操作,类似于Android中系统发送的广播。</p>
- 我们可以控制通知的发送监听,可以在任何地方任何时机发送一个通知。类似于Android中开发者自己发送的广播。</p>
- 通知的使用场景比KVO更为广泛些
- 通知需要一个发送notification的对象,一般是notificationCenter,来通知观察者。KVO是直接通知到观察对象,并且逻辑非常清晰,实现步骤简单。
- 总之,KVO操作没有通知灵活。但是KVO也有自己的优点,比如可以记录新旧值,通知就比较麻烦,所以在使用的时候还是具体问题具体分析,一般监听属性值的变化,最好还是用KVO。
NSNotificationCenter与Delegate的比较
<p style = "text-indent:2em;font-size = 20px;">通知比Deleagte可以实现更大跨度的通信机制,可以为两个无引用关系的两个对象进行通信,即事件发出者和响应者可以没有任何耦合关系。</p>