Head_First设计模式(二)----观察者模式

2016-06-17  本文已影响44人  河马流星锤

简述设计模式

观察者模式: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

使用条件: 当你需要将改变通知所有的对象时,而你又不知道这些对象的具体类型,此时就可以使用观察者模式。 改变发生在同一个对象中,并在别的地方需要将相关的状态进行更新。

例子: Head_First中是以气象局天气预报为例子接下来会给出代码, 书中是以JAVA为语言基础给出的例子, 这里作者主要做的是iOS开发, 所以翻译成OC进行举例。

相关知识点和思路

涉及到的知识点

主要包括****: NSNotification, ****封装和协议的使用****(****对应部分会在代码中详细标注****)****。

思路分析

在开发过程中会经常遇到一些实际情况, 这里直接拿书中的气象台来举例了。

设计原理

图形解析: 首先是数据采集部分, 和我们程序员关系不大。我们主要负责的是从WeatherData取数据到各个发布板展示的过程。即上图右半部分, 这里请不要纠结数据咋来的, 我们只当他采集到了并且付给了我们的WeatherData对象。

添加观察者

我们想要得到实时变化的数据, 首先需要添加观察者, 即我们想知道天气变化,我们需要打开电视看气象台卫视一样。

订阅

****这里的主题对象即上文的****WeatherData, ****狗**** ****猫**** ****老鼠**** ****这些对象即对应的各个显示装置****

移除观察者

当我们不在关心天气变化时, 我们换了台, 即不在关注WeatherData, 这里我们就要移除观察者。 (********务必在不用时移除****, ****否则会很蛋疼********)

取消订阅

很显然这里如果我们使用观察者很容易解决温度, 气压等实时更新的问题, 而且也符合我们的订阅和取消订阅原则。

代码实现

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"]; /* 也需要移除, 也需要移除, 也需要移除 */
}

结果

运行结果
如果上文options 中只有1 则打印结果时不会显示old

KVO结果

观测者标准模式

这种方式需要自己定义协议, 和实现方法实现观测。

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]


上一篇下一篇

猜你喜欢

热点阅读