观察者模式(有时又被称为发布-订阅模式)
在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。
这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
比如我们订阅杂志, 会有一个订阅服务中心, 他负责管理期刊号, 添加用户 和 发送期刊
这里订阅服务中, 期刊, 用户 我们看做3个因素:
用户要订阅, 需要遵循一定的订阅规范(协议)
期刊要能记录有哪些订阅用户
订阅服务中心负责管理, 当有某一期刊更新时, 通知该期刊的订阅用户或者发送新期刊给订阅用户
下面我们依照这个思路构造工程
这里把订阅服务中心看做一个对象, 并把它设计成一个单例 因为一般只会有一个订阅服务中心管理所有的期刊和用户
订阅服务中心对象有以下功能:
添加/删除期刊, 给某一期刊添加/删除订阅用户, 检查期刊号是否存在, 当有更新时通知订阅用户
期刊管理订阅用户信息时, 不能持有订阅用户对象造成内存泄露, 所以用NSHashTable来保存用户信息
用户要遵守一个订阅规范(协议)
SubscriptionCustomerProtocol.h
#import <Foundation/Foundation.h>
@protocol SubscriptionCustomerProtocol <NSObject>
@required
- (void)subscriptionMessage:(id)message subscriptionNumber:(NSString *)subscriptionNumber;
@end
下面构造订阅服务中心对象-用单例模式
SubscriptionServiceCenter.h
#import <UIKit/UIKit.h>
#import "SubscriptionCustomerProtocol.h"
@interface SubscriptionServiceCenter : NSObject
/**
初始化单例方法
@return 返回单例对象
*/
+ (instancetype)shareInstance;
/**
alloc初始化方法
@param zone 地址空间
@return 返回单例对象
*/
+ (id)allocWithZone:(struct _NSZone *)zone;
/**
copy方法
@param zone 地址空间
@return 返回单例对象
*/
- (id)copWithZone:(struct _NSZone *)zone;
#pragma mark - 维护订阅信息
/**
创建订阅号
@param subscriptionNumber 订阅号码
*/
- (void)createSubscriptionNumber:(NSString *)subscriptionNumber;
/**
删除订阅号
@param subscriptionNumber 订阅号码
*/
- (void)removeSubscriptionNUmber:(NSString *)subscriptionNumber;
#pragma mark - 维护客户信息
/**
添加客户到具体的订阅号中
@param customer 客户
@param subscriptionNumber 订阅号码
*/
- (void)addCustomer:(id <SubscriptionCustomerProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber;
/**
从具体订阅号中移除客户
@param customer 客户
@param subscriptionNumber 订阅号码
*/
- (void)removeCustomer:(id <SubscriptionCustomerProtocol>)customer withSubcriptionNumber:(NSString *)subscriptionNumber;
/**
发送消息到具体的订阅号中
@param message 消息
@param subscriptionNumber 订阅号码
*/
- (void)sendMessage:(id)message toSubscriptionNumber:(NSString *)subscriptionNumber;
/**
获取用户列表
@param subscriptionNumber 订阅号码
@return 返回用户列表
*/
- (NSHashTable *)existSubscriptionNumber:(NSString *)subscriptionNumber;
@end
SubscriptionServiceCenter.m
#import "SubscriptionServiceCenter.h"
static NSMutableDictionary *_subscriptionDictionary = nil;
@implementation SubscriptionServiceCenter
static SubscriptionServiceCenter *_instance = nil;
+ (instancetype)shareInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_subscriptionDictionary = [NSMutableDictionary dictionary];
_instance = [[super allocWithZone:NULL] init];
});
return _instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [SubscriptionServiceCenter shareInstance];
}
- (id)copWithZone:(struct _NSZone *)zone {
return [SubscriptionServiceCenter shareInstance];
}
- (void)createSubscriptionNumber:(NSString *)subscriptionNumber {
NSParameterAssert(subscriptionNumber);
NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
if (hashTable == nil) {
hashTable = [NSHashTable weakObjectsHashTable];
[_subscriptionDictionary setObject:hashTable forKey:subscriptionNumber];
}
}
- (void)removeSubscriptionNUmber:(NSString *)subscriptionNumber {
NSParameterAssert(subscriptionNumber);
NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
if (hashTable) {
[_subscriptionDictionary removeObjectForKey:subscriptionNumber];
}
}
- (void)addCustomer:(id <SubscriptionCustomerProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber {
NSParameterAssert(customer);
NSParameterAssert(subscriptionNumber);
NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
[hashTable addObject:customer];
}
- (void)removeCustomer:(id <SubscriptionCustomerProtocol>)customer withSubcriptionNumber:(NSString *)subscriptionNumber {
NSParameterAssert(subscriptionNumber);
NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
[hashTable removeObject:customer];
}
- (void)sendMessage:(id)message toSubscriptionNumber:(NSString *)subscriptionNumber {
NSParameterAssert(subscriptionNumber);
NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber];
if (hashTable) {
NSEnumerator *enumerator = [hashTable objectEnumerator];
id <SubscriptionCustomerProtocol> object = nil;
while (object = [enumerator nextObject]) {
if ([object respondsToSelector:@selector(subscriptionMessage: subscriptionNumber:)]) {
[object subscriptionMessage:message subscriptionNumber:subscriptionNumber];
}
}
}
}
- (NSHashTable *)existSubscriptionNumber:(NSString *)subscriptionNumber {
return [_subscriptionDictionary objectForKey:subscriptionNumber];
}
@end
下面在Controller中实现, Controller作为用户即观察者
#import "ViewController.h"
#import "SubscriptionCustomerProtocol.h"
#import "SubscriptionServiceCenter.h"
static NSString * SCIENCE = @"SCIENCE";
@interface ViewController () <SubscriptionCustomerProtocol>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//创建一个订阅服务中心单例
SubscriptionServiceCenter *center = [SubscriptionServiceCenter shareInstance];
//创建一个订阅号
[center createSubscriptionNumber:SCIENCE];
//添加一个用户
[center addCustomer:self withSubscriptionNumber:SCIENCE];
//发送一个通知消息
[center sendMessage:@"有新的期刊啦" toSubscriptionNumber:SCIENCE];
}
#pragma mark - SubscriptionCustomerProtocol
- (void)subscriptionMessage:(id)message subscriptionNumber:(NSString *)subscriptionNumber {
NSLog(@"期刊号: %@ 收到消息: %@", subscriptionNumber, message);
}
@end
Cocoa touch中的KVO和NSNotificationCenter的原理是观察模式的很好实现, 下面用代码分别演示下用法
KVO的用法
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.model = [Model new];
//添加KVO
[self.model addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
//发送信息, 通过修改属性
self.model.name = @"v1.0";
}
#pragma mark - KVO方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"%@", change);
}
- (void)dealloc {
//移除KVO
[self.model removeObserver:self
forKeyPath:@"name"];
}
NSNotificationCenter的用法
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//添加
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(notificationCenterEvent:)
name:@"SCIENCE"
object:nil];
//发送信息
[[NSNotificationCenter defaultCenter] postNotificationName:@"SCIENCE"
object:@"v1.0"];
}
#pragma mark - 通知中心方法
- (void)notificationCenterEvent:(id)sender {
NSLog(@"%@", sender);
}
- (void)dealloc {
//移除通知中心
[[NSNotificationCenter defaultCenter] removeObserver:self
forKeyPath:@"SCIENCE"];
}