观察者模式(设计基础:KVC / KVO)
什么是观察者模式?
在工程中,一些类去观察A类,当‘A类’发生变化时。
这些观察类就会受到消息,做出相应的反应
什么时候使用观察者模式?
当一个类需要发送消息给多个类的时候,就用观察者模式
一对多的发送消息
KVC的由来,在iOS2.0之前,并没有属性来访问类中的实例变量,那时候开发人员用‘键值编码’的方式来访问类的实例变量,即‘KVC’方式。
什么叫KVC?
KVC :Key-Value Coding 键值编码
通过制定表示要访问的属性名字的字符串标识符,可以进行类的属性读取和设置。
KVC提供了一种在运行时而非编译时动态访问对象属性与实例变量的方式。
- (void)setValue:(nullable id)value forKey:(NSString *)key;
/*
参数①: 要赋的值
参数②:需要赋值的 成员变量或者属性 (需要写字符串)
*/ - (nullable id)valueForKey:(NSString *)key;
/*
参数②:需要获得值的 成员变量或者属性 (需要写字符串)
*/
创建一个Student类 .h文件
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Student : NSObject
{
NSString *_name;
NSNumber *_age;
Book *_book;
}
@property(nonatomic ,copy)NSString *address;
@end
.m文件
#import "Student.h"
@implementation Student
//重写description方法
- (NSString *)description
{
return [NSString stringWithFormat:@"name:%@,age:%@,address:%@", _name,_age,_address];
}
mian函数里面:
Student *stu = [Student new];
// 赋值
[stu setValue:@"Rick" forKey:@"_name"];
[stu setValue:@25 forKey:@"_age"];
[stu setValue:@"GZ" forKey:@"_address"];
NSLog(@"%@",stu);
// 访问
NSString *stuName= [stu valueForKey:@"_name"];
NSNumber *stuAge = [stu valueForKey:@"_age"];
NSString*stuAddress = [stu valueForKey:@"_address"];
NSLog(@"stuName:%@,stuAge:%@,stuAddress:%@",stuName,stuAge,stuAddress);
KVC是很暴力的,你把属性设置为readonly它也同样能够给属性赋值,甚至私有的成员变量都能 搞到,只要你知道变量名字就行,这就是
eg:
在Student的.m文件中加
//延展:私有成员变量
@interface Student ()
{
NSString * haha;
}
@end
//在mian函数里加上这两句
[stu setValue:@"私有的成员变量也被我访问到了,KVC很暴力吧" forKey:@"haha"];
NSLog(@"%@",[stu valueForKey:@"haha"]);
结果就会出现这样:
什么叫KVO
KVO :Key-Value Observing 键值观察
在我们的编程过程中,我们经常的需要去观察一个变量的值变化,当目标改变时需要及时的做出变化.
这时候就到了KVO大展身手了
使用KVO的三部曲:
1.注册观察者
2.定义KVO的回调方法(当目标发生变化的时候要执行的方法)
3.移除观察者
引用一句名言:”talk is cheap, show me your code!”
废话少说 , 放码过来 !
首先需要创建两个类
一个英雄类,一个是观察者类
Observer.h文件
#import <Foundation/Foundation.h>
#import "Hero.h"
@interface Observe : NSObject
//把英雄作为Observer的一个属性
@property(nonatomic,retain)Hero *hero;
//初始化便利函数
-(id)initWithHero:(Hero*)hero;
@end
Observer.m文件
#import "Observe.h"
@implementation Observe
-(id)initWithHero:(Hero *)hero
{
self = [super init];
if (self) {
self.hero = hero;
// 注册观察者
/*
NSKeyValueObservingOptionNew //把更改后的值提供给观察者的回调方法
NSKeyValueObservingOptionOld //把更改前的值提供给观察者的回调方法
NSKeyValueObservingOptionInitial
NSKeyValueObservingOptionPrior
*/
/**
* - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
例子中是:hero 把Obsever注册为观察自己的对象,所以hero是被观察者,Obsever是观察者.(我们的目标是hero,就需要用hero对象来调用上面的方法.)
!KVO是被观察者找人来观察自己! 下面会讲到NSNotification (通知)与KVO相似,但是有区别
参数1:观察者,接收观察信息的对象,常为self
参数2:需要观察的东东 (KeyPath 键值路径)
参数3:观察的方法 (可以多个)
参数4:上下文内存区,通常为nil
*/
[self.hero addObserver:self forKeyPath:@"_HP" options:NSKeyValueObservingOptionNew|
NSKeyValueObservingOptionOld
context:nil];
}
return self;
}
//定义KVO回调函数,
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"change = %@",change[NSKeyValueChangeNewKey]);
/*
change是一个字典,包含的key如下:
NSKeyValueChangeKindKey
NSKeyValueChangeNewKey //新值
NSKeyValueChangeOldKey //旧值
NSKeyValueChangeIndexesKey
NSKeyValueChangeNotificationIsPriorKey
*/
}
- (void)dealloc
{
/**
* NSObject 类定义了一个方法叫做dealloc,当个对象没有拥有者并且内存要被回收时,系统会自动调用dealloc方法.dealloc的责任是释放对象自己的内存,释放所有它控制的资源,包括释放所有势力变量的所有权.
*/
// 移除观察者
[self.hero removeObserver:self forKeyPath:@"_HP"];
NSLog(@"我死了,我还会再回来的!!!");
}
@end
NSNotification(通知)
什么是通知?
NSNotification
通过学习我们的KVO,我们发现'KVO'是一种简单的观察者设计模式,涉及到两个对象,分别是 观察者 和 被观察者. 这种方式实质有很大的局限性,那么OC的'Foundation'框架又为开发者提供了新的一种观察者设计模式,即"通知"
通知,是一种发送给一个或者多个观察者,用来通知其在程序中发生了某个事件的消息.Cocoa中的通知机制遵循的是一种广播模式.它是一种程序中事件发起者或者处理者和其他想要知道该事件的对象 沟通的一种方式.消息的接受者,也就是观察者相应该事件来改变自己的UI.行为或状态.
通知的创建:
两个类方法:
//+ (instancetype)notificationWithName:(NSString *)aName object:(nullable id)anObject;
//+ (instancetype)notificationWithName:(NSString )aName object:(nullable id)anObject userInfo:(nullable NSDictionary )aUserInfo;
NSNotification *nf1 = [NSNotification notificationWithName:@"notificationName1" object:self];//object:通知的发起人
NSNotification *nf2 = [NSNotification notificationWithName:@"notificationName2" object:self userInfo:@{@"content":@"hello world"}];
/**
* 其中:
1.name:通知的名称,最好是英文,用来识别通知的对象
2.object:表示通知的发起人
3.uesrInfo:表示通知的内容
*/
通知中心:
这个通知就像现实中的邮件,邮件都需要邮局发送给接受人.OC中也一样,'Foundation'框架定义了一个单例,通知中心,NSNotificationCenter来统一发送通知的实例对象给观察者
创建通知中心:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter]
eg:
NSNotificationCenter *centet = [NSNotificationCenter defaultCenter];
// 通知中心区发送消息
[centet postNotification:nf2];
//不过我们一般不这么写,哈哈,这样写是便于理解,这样多麻烦啊.看下面的例子吧.
例子:模拟气象局发送通知给手机用户,手机用户接收到后做出回应.
首先,我们应该明确建立通知机制的三部曲(与KVO是非常类似的):
1.注册相关的监听者(可能是多个),并实现监听后需要执行的方法.
2.在需要的时候被监听者去通知中心发送通知.
3.在dealloc的方法中移除监听者.
Weather.h文件
//气象局
@interface Weather : NSObject<NSCopying>
-(void)sendMessage;
@end
.m文件.
@implementation Weather
-(void)sendMessage
{
// 发送通知
/**
*
* 三个发送通知的方法
- (void)postNotification:(NSNotification *)notification;
参数: 通知(如上面的 那个 'nf2'的通知 ).
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
参数同下 1. 2.
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
1.通知的名字.
2.发送通知的对象,一般为self.
3.发送的通知信息(字典).
*/
[[NSNotificationCenter defaultCenter] postNotificationName:@"WeatherAndPhoneUser" object:self userInfo:@{@"weather":@"Sunny"}];
}
@end
PhoneUser.h文件默认
.m文件
@implementation PhoneUser
- (instancetype)init
{
self = [super init];
if (self) {
// 注册监听者
// 到通知中心去让自己成为某个通知的监听对象
/**
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
* 参数:
1.要去接收通知的对象
2.接受通知响应的方法
3.接收通知的名称(指定监听哪个通知)
4.指定接收,发起通知的对象,一般比较少用到.
*/
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationAction:) name:@"WeatherAndPhoneUser" object:nil];
}
return self;
}
//收到通知回调的方法
-(void)notificationAction:(NSNotification*)notification
{
NSDictionary *dictionary = notification.userInfo;
NSLog(@"今天的天气:%@",dictionary[@"weather"]);
}
//移除通知
- (void)dealloc
{
//移除某个具体的通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"WeatherAndPhoneUser" object:nil];
// 移除所有通知的监听者
// [[NSNotificationCenter defaultCenter] removeObserver:self];
}
main.m文件
很简单,就三句代码
Weather *weather = [Weather new];
PhoneUser *phoneUesr = [PhoneUser new];
// 调用方法,发送通知
[weather sendMessage];
因为通知中心是一个系统的单例对象,所以所有的通知都会发到这来,在iOS的UI开发中,经常会用通知来在页面与页面之中传递信息.