Head_First设计模式(二)----观察者模式
简述设计模式
观察者模式: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
使用条件: 当你需要将改变通知所有的对象时,而你又不知道这些对象的具体类型,此时就可以使用观察者模式。 改变发生在同一个对象中,并在别的地方需要将相关的状态进行更新。
例子: Head_First中是以气象局天气预报为例子接下来会给出代码, 书中是以JAVA为语言基础给出的例子, 这里作者主要做的是iOS开发, 所以翻译成OC进行举例。
相关知识点和思路
涉及到的知识点
主要包括****: NSNotification, ****封装和协议的使用****(****对应部分会在代码中详细标注****)****。
思路分析
在开发过程中会经常遇到一些实际情况, 这里直接拿书中的气象台来举例了。
设计原理图形解析: 首先是数据采集部分, 和我们程序员关系不大。我们主要负责的是从WeatherData取数据到各个发布板展示的过程。即上图右半部分, 这里请不要纠结数据咋来的, 我们只当他采集到了并且付给了我们的WeatherData对象。
添加观察者
我们想要得到实时变化的数据, 首先需要添加观察者, 即我们想知道天气变化,我们需要打开电视看气象台卫视一样。
订阅****这里的主题对象即上文的****WeatherData, ****狗**** ****猫**** ****老鼠**** ****这些对象即对应的各个显示装置****
移除观察者
当我们不在关心天气变化时, 我们换了台, 即不在关注WeatherData, 这里我们就要移除观察者。 (********务必在不用时移除****, ****否则会很蛋疼********)
取消订阅很显然这里如果我们使用观察者很容易解决温度, 气压等实时更新的问题, 而且也符合我们的订阅和取消订阅原则。
代码实现
-
观察者模式在iOS种大体可分为三类
-
1.****Notification(****这是苹果给的类拿过来直接用就行****)****
-
2.****KVO****
-
3.****标准方法****
1.Notification
官方API
/* 获取系统的默认通知中心 */
+(NSNotificationCenter *)defaultCenter;
/* 添加观察者 */
/**
* @observer: 主题对象(上文的WeatherData)
* @(SEL)aSelector: 主题对象改变时通知观察者执行的方法
* @aName: 确认身份的名字(通过这个连接主题和观察者)
* @anObject: 想了主题解数据改变并得到回复的观察者(上文的狗,猫等显示装置)
*/
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
/**
* @name: 确认身份的名字(通过这个连接主题和观察者)
* @obj: 想了解主题数据改变并得到回复的观察者(上文的狗,猫等显示装置),如果nil 所有观察者都会收到
* @queue: 有反馈结果时需返回到的线程一般为[NSOperationQueue mainQueue]
* @block: 这里可以获得改变的数据和做对应处理即上个方法中的@(SEL)aSelector所执行的操作
*/
- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);
/* 发送数据 */
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;/* @aUserInfo: 是用于装主题数据改变的字典, 就是把这个传给观察者 */
实际使用
/* 添加观察者(订阅) */
- (void)viewDidLoad
{
[super viewDidLoad];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; /* 获取系统的通知中心单利 */
[notificationCenter addObserverForName:@"valueChange" object:@"123" queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
/**
* @note: 里面有传过来的数据 直接使用note.userInfo调用。
*/
}];
}
/* 主题数据改变处理发送部分 */
- (IBAction)btnNotificationCenterTest:(id)sender { /* 这是xib直接托的控件 */
/* 通过点击button发送通知 */
[notificationCenter postNotificationName:@"valueChange" object:@"123" userInfo:@{@"key":@"change"}];
}
/* 从通知中心(NSNotificationCenter)中移除观察者 */
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"valueChange" object:@"123"];
/* 这里需要注意的一般都是在界面消失的时候移除观察者, 当然也有例外, 如果是从别的界面向这个界面传值时, 需要这个界面事先是存在的(即已经创建), 这时就不能在界面消失的方法里移除, 需要处理的是无论界面消失或存在都只添加一次观察者, 不要重复添加 */
}
PS: 观察者需要先添加, 不然发送了也收不到。就想订阅报纸, 你还没定呢, 送报纸的肯定不会给你送。所以需要先添加之后再发送数据。
这种方法我比较喜欢, 配合全局变量可以随便传值无国界跨时代, 而且快。比之属性传值, 代理传值好的不是一星半点。
KVO
解释: KVO也是苹果提供的方法, 为对象添加监测对象属性变化的观察者。比较常见的就是很多APP种通过滑动tableview控制NavigationBar变透明或者显示的操作。
官方API
/**
* @observer: 添加观测的对象(上文中的WeatherData)
* @keyPath: 对象的可变属性(温度, 压强)
* @options: 这是一个枚举 一般会填 1 | 2 属性变换的新旧值
* @context: 上下文, 一般用不到填nil
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
实际使用
单独写一个KVO观测者类继承OBJECT, 名字随便我这里叫KVOObserver。这个单独封装出来好管理
在建一个要观察的对象类 , 名字叫KVOObject。
观察者KVOObserver
/* KVOObserver的.h */
/* 封装 */
#import <Foundation/Foundation.h>
@interface KVOObserver : NSObject
@end
#import "KVOObserver.h"
/* KVOObserver的.m */
@implementation KVOObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"%@", change);
NSLog(@"KVO:值发生了改变");
}
@end
主题对象(WeatherData)KVOObject
/* KVOObject.h */
/* 只是为举例子创建的类, 工程中使用时不要这个, 直接将观察者添加给你在使用的类, 观察其属性即可 */
#import <Foundation/Foundation.h>
@interface KVOSubject : NSObject
@property (nonatomic, strong) NSString changeableProperty;/* 供观察的属性 */
@end
/* KVOObject.m */
#import "KVOSubject.h"
@implementation KVOSubject
@end
使用
/* 引入上面类的头文件 */
#import "KVOSubject.h"
#import "KVOObserver.h"
#pragma mark- KVO
- (IBAction)btnKVOObservationTest:(id)sender { /* 点击事件 */
KVOSubject *kvoSubj = [[KVOSubject alloc] init];
KVOObserver *kvoObserver = [[KVOObserver alloc] init];
[kvoSubj addObserver:kvoObserver forKeyPath:@"changeableProperty"
options:1 | 2 context:nil]; /* @options: 1|2 代表 NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld */
kvoSubj.changeableProperty = @"我是GS";
kvoSubj.changeableProperty = @"我是GSSS111111"; /* 改值 */
//[kvoSubj removeObserver:kvoObserver forKeyPath:@"changeableProperty"]; /* 也需要移除, 也需要移除, 也需要移除 */
}
- PS: 别忘记移除
结果
运行结果
KVO结果
如果上文options 中只有1 则打印结果时不会显示old
观测者标准模式
这种方式需要自己定义协议, 和实现方法实现观测。
Subject和Observer(观察者)的协议(protocol)创建
这里添加的只是协议方法所以只有.h
实现的部分会写在下一块
StandardObserver观察者协议
/* StandardObserver.h观察者协议 */
#import <Foundation/Foundation.h>
@protocol StandardObserver <NSObject>
- (void)valueChanged:(NSString **)valueName newValue:(NSString *) newValue; /* 主题值改变的方法 */
@end
StandardSubject主题对象协议
/* StandardSubject.h 主题对象协议 */
#import <Foundation/Foundation.h>
#import "StandardObserver.h"
@protocol StandardSubject <NSObject>
- (void)addObserver:(id<StandardObserver>)observer; /* 添加观察者 */
- (void)removeObserver:(id<StandardObserver>)observer; /* 移除观察者 */
- (void)notifyObjects; /* 通知对应的观察者对象 */
@end
主题对象创建
主题对象的.h
#import <Foundation/Foundation.h>
#import "StandardSubject.h"
@interface StandardSubjectImplementation : NSObject <StandardSubject>
{/* 签主题对象协议 */
@private NSString *_valueName;
@private NSString *_newValue;
}
@property (nonatomic, strong) NSMutableSet **observerCollection;/** 主题对象的观察者集合 */
-(void)changeValue:(NSString *)valueName andValue:(NSString *) newValue;
@end
主题对象的.m
#import "StandardSubjectImplementation.h"
@implementation StandardSubjectImplementation
/* 主题的观察者对象初始化, 这里使用了懒加载 */
- (NSMutableSet *)observerCollection
{
if (_observerCollection == nil)
_observerCollection = [[NSMutableSet alloc] init];
return _observerCollection;
}
#pragma mark -
#pragma mark 协议方法实现
/* 添加观察者 */
-(void) addObserver:(id<StandardObserver>)observer
{
[self.observerCollection addObject:observer];
}
/* 移除观察者 */
-(void) removeObserver:(id<StandardObserver>)observer
{
[self.observerCollection removeObject:observer];
}
/* 通知观察者 */
-(void)notifyObjects
{
for (id<StandardObserver> observer in self.observerCollection) {
[observer valueChanged: _valueName newValue:_newValue];
}
}
-(void)changeValue:(NSString *)valueName andValue:(NSString *) newValue
{
_newValue = newValue;
_valueName = valueName;
[self notifyObjects];
}
@end
观察者对象
观察者对象.h
#import <Foundation/Foundation.h>
#import "StandardObserver.h"
@interface SomeSubscriber : NSObject <StandardObserver>/* 签观察者协议 */
@end
观察者对象.m
#import "SomeSubscriber.h"
@implementation SomeSubscriber
/* 协议方法实现 */
-(void)valueChanged:(NSString *)valueName newValue:(NSString *)newValue
{
NSLog(@"SomeSubscriber输出: 值 %@ 已变为 %@", valueName, newValue);
}
@end
使用
别忘记引入头文件
#pragma mark- 标准Observer
- (IBAction)btnStandardObservationTest:(id)sender {
StandardSubjectImplementation *subj = [[StandardSubjectImplementation alloc] init];
SomeSubscriber *someSubscriber = [[SomeSubscriber alloc] init];
[subj addObserver:someSubscriber];
[subj changeValue:@"version" andValue:@"1.0.0"];
}
打印结果
写的时候是添加了两个观察者对象所以显示两个结果
这里写图片描述使用之后别忘记移除
[subj removeObserver:someSubscriber];
PS: 以上都是比较简单的应用, 实际使用时比这里要复杂一些, 但是也很容易理解。 对于观察者模式, 只要理清了思路, 是十分好用的。而且前两种都是苹果为我们内置的观察者, 使用起来更方便。
[toc]