键值观察(KVO)是基于键值编码的一种技术。
利用键值观察可以注册成为一个对象的观察者,在该对象的某个属性变化时收到通知。
被观察对象需要编写符合KVC标准的存取方法,编写键值观察分为以下三步:
(1)注册成为观察者。
(2)定义KVO的回调。
(3)移除观察者。
+新建一个类Student,属性为name,age。
@interface Student : NSObject @PRoperty(copy,nonatomic) NSString * name; @property(nonatomic) int age; @end
新建一个Parent类,作为观察者。main函数:
Student * student = [Student new]; Parent * parent = [Parent new]; //建立观察者模式关系,注册观察者 [student addObserver:parent forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:parent forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
第一个参数:(student)被观察者。
第二个参数:(parent)观察者。
第三个参数:(forKeyPath)键路径参数,要观察的键路径,这里观察name和age。
第四个参数:(options)标识KVO希望变化如何传递给观察者,可以使用|进行多选,这里观察变化的新值和旧值。
第五个参数:上下文内存区,通常为nil
+在parent.m的文件中定义KVO的回调。
@implementation Parent //观察者实现键值观察方法,要作为观察者,需要实现这个方法。 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { id oldValue = [change objectForKey:NSKeyValueChangeOldKey]; id newValue = [change objectForKey:NSKeyValueChangeNewKey]; NSLog(@"家长观察对象:对象:%@的属性%@的值发生了改变,旧值为:%@,新值为:%@",object,keyPath,oldValue,newValue); }
其中change是一个字典。
+如果student的name或age发生变化(最后要移除观察者):
Student * student = [Student new]; Parent * parent = [Parent new]; //建立观察者模式关系,注册观察者 [student addObserver:parent forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:parent forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; student.age = 10; student.name = @"小明"; //结束之前需要移除观察者 [student removeObserver:parent forKeyPath:@"age"]; [student removeObserver:parent forKeyPath:@"name"];
那么parent上定义的的回调会自动执行,打印结果:
当然也可以多个对象观察一个对象,新建teacher类,这样就有两个观察者:
Student * student = [Student new]; Parent * parent = [Parent new]; Teacher * teacher = [Teacher new]; //建立观察者模式关系,注册观察者 [student addObserver:parent forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:teacher forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:parent forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; student.age = 10; student.name = @"小明"; //结束之前需要移除观察者 [student removeObserver:parent forKeyPath:@"age"]; [student removeObserver:parent forKeyPath:@"name"]; [student removeObserver:teacher forKeyPath:@"age"];
****************************************
另外一种情况:新建Family类,让student成为它的一个属性,然后让Family成为student的观察者,这样注册观察者的方法可以写在Family的init方法中,移除观察者写在Family的dealloc方法中:
Family.h文件:
@interface Family : NSObject @property(strong,nonatomic) Student * student; -(id) initWithStudent:(Student *)student; @end
Family.m文件:
@implementation Family -(id)initWithStudent:(Student *)student { if(self = [super init]){ self->_student = student; //让当前对象成为student的观察者 [self->_student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil]; } return self; } //观察者实现键值观察方法,要作为观察者,需要实现这个方法。 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { id oldValue = [change objectForKey:NSKeyValueChangeOldKey]; id newValue = [change objectForKey:NSKeyValueChangeNewKey]; NSLog(@"家庭观察对象:对象:%@的属性%@的值发生了改变,旧值为:%@,新值为:%@",object,keyPath,oldValue,newValue); } -(void)dealloc { NSLog(@"Family dealloc..."); //移除观察者 [self->_student removeObserver:self forKeyPath:@"name"]; //ARC所以不用写super dealloc } @end
main:
Student * student = [Student new]; Family * family = [[Family alloc]initWithStudent:student]; student.name = @"hehe";
可以看到KVO的方式耦合度较高,类之间有直接的关系。接下来看通知。
通知是iOS开发框架中的一种设计模式,通知一般用于M、V、C之间的信息传递。比如设置页面、设置App皮肤。
NSNotification
使用通知之前,我们要创建通知对象。 Notification对象有两个重要的成员变量: name 和 object。一般name用来唯一标示一个通知对象,object指通知发送者。
Notification对象包含一个参数,就是字典(可选参数),这个字典中存储一些传值过程中的信息,供接收者使用。系统要求这个参数是不可变字典。
NSNotificationCenter
通知创建好后就可以在必要的时候发送通知,发送通知的时候,需要一个控制中心来发送通知,这个控制中心就是通知中心。
通知中心是通知机制架构的大脑。它允许我们注册通知监听者、发送通知、移除通知监听者。
一般系统通知不需要我们发送通知。只需要我们注册通知监听者、 移除通知监听者。比如监听视频是否播放完成。
栗子,Teacher发送通知。学生接收:
页面结构:
老师有一个发送方法。Teacher.h文件:
@interface Teacher : NSObject - (void) sendMessage; @end
Teacher.m文件:
#import "MacroDefinition.h" @implementation Teacher - (void)sendMessage { NSNotification * myNotification = [NSNotification notificationWithName:kMyNotificationName object:self userInfo:@{@"content":@"同学们,星期一放假,不用来了"}]; //发送通知 //需要使用通知中心来发送。 //通知中心是一个单例模式的 NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; [center postNotification:myNotification]; } @end
其中NSNotification中的notificationWithName因为别的地方也会用到,所以写成了宏定义放到了单独的.h文件MacroDefinition中,文件中写了:#define kMyNotificationName @"customNotification" 。userinfo是一个字典,可以存通知的内容。
先写上Student的接收通知后的回调方法:
Student.h文件:
@interface Student : NSObject //可以跟一个参数,但是只能是NSNotification对象。 -(void)receiveMessage:(NSNotification *) notification; @end
Student.m文件:
@implementation Student - (void)receiveMessage:(NSNotification *)notification { NSLog(@"对象%@发出来名称为:%@的通知,附加数据为:%@",notification.object,notification.name,[notification.userInfo objectForKey:@"content"]); }
main引入:
#import "MacroDefinition.h"
#import "Teacher.h"
#import "Student.h"
Teacher * teacher = [Teacher new]; Student * student1 = [Student new]; Student * student2 = [Student new]; Student * student3 = [Student new]; //订阅通知中心 NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; //注册,第一个参数是观察者,第二个参数是观察者拿到自己关心的通知之后,所要调用的方法,第三个参数为观察者关心的通知的名称,第四个参数为观察者关心的发出对象。 [center addObserver:student1 selector:@selector(receiveMessage:) name:kMyNotificationName object:nil]; [center addObserver:student2 selector:@selector(receiveMessage:) name:kMyNotificationName object:nil]; [center addObserver:student3 selector:@selector(receiveMessage:) name:kMyNotificationName object:nil]; //发送 [teacher sendMessage]; //道理上来说应该移除订阅者 [center removeObserver:student1 name:kMyNotificationName object:nil]; [center removeObserver:student2 name:kMyNotificationName object:nil]; [center removeObserver:student3 name:kMyNotificationName object:nil];
打印结果:
通知中心是一个单例模式的,所以kMyNotificationName,main和Teacher.m中的是同一个,所以这两个文件用的宏定义是一样的。
这样这两个类并没有直接的关系只是通过通知中心来传递消息。关于通知的更多内容后边再补充吧=。=
1:KVO的类是由直接联系的,耦合度较高。而通知是没有直接联系的。
2:KVO是只要属性发生改变,观察者就会被响应。通知是被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。