Objective-C 观察者模式--简单介绍和使用
观察者模式(有时又被称为发布-订阅模式)
在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。
这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
比如我们订阅杂志, 会有一个订阅服务中心, 他负责管理期刊号, 添加用户 和 发送期刊
这里订阅服务中, 期刊, 用户 我们看做3个因素:
用户要订阅, 需要遵循一定的订阅规范(协议)
期刊要能记录有哪些订阅用户
订阅服务中心负责管理, 当有某一期刊更新时, 通知该期刊的订阅用户或者发送新期刊给订阅用户
下面我们依照这个思路构造工程
这里把订阅服务中心看做一个对象, 并把它设计成一个单例 因为一般只会有一个订阅服务中心管理所有的期刊和用户
订阅服务中心对象有以下功能:
添加/删除期刊, 给某一期刊添加/删除订阅用户, 检查期刊号是否存在, 当有更新时通知订阅用户
期刊管理订阅用户信息时, 不能持有订阅用户对象造成内存泄露, 所以用NSHashTable来保存用户信息
用户要遵守一个订阅规范(协议)
SubscriptionCustomerProtocol.h
1 #import <Foundation/Foundation.h> 2 3 @protocol SubscriptionCustomerProtocol <NSObject> 4 5 @required 6 - (void)subscriptionMessage:(id)message subscriptionNumber:(NSString *)subscriptionNumber; 7 8 @end
下面构造订阅服务中心对象-用单例模式
SubscriptionServiceCenter.h
1 #import <UIKit/UIKit.h> 2 #import "SubscriptionCustomerProtocol.h" 3 4 @interface SubscriptionServiceCenter : NSObject 5 6 /** 7 初始化单例方法 8 9 @return 返回单例对象 10 */ 11 + (instancetype)shareInstance; 12 13 /** 14 alloc初始化方法 15 16 @param zone 地址空间 17 @return 返回单例对象 18 */ 19 + (id)allocWithZone:(struct _NSZone *)zone; 20 21 /** 22 copy方法 23 24 @param zone 地址空间 25 @return 返回单例对象 26 */ 27 - (id)copWithZone:(struct _NSZone *)zone; 28 29 #pragma mark - 维护订阅信息 30 /** 31 创建订阅号 32 33 @param subscriptionNumber 订阅号码 34 */ 35 - (void)createSubscriptionNumber:(NSString *)subscriptionNumber; 36 37 /** 38 删除订阅号 39 40 @param subscriptionNumber 订阅号码 41 */ 42 - (void)removeSubscriptionNUmber:(NSString *)subscriptionNumber; 43 44 #pragma mark - 维护客户信息 45 /** 46 添加客户到具体的订阅号中 47 48 @param customer 客户 49 @param subscriptionNumber 订阅号码 50 */ 51 - (void)addCustomer:(id <SubscriptionCustomerProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber; 52 53 /** 54 从具体订阅号中移除客户 55 56 @param customer 客户 57 @param subscriptionNumber 订阅号码 58 */ 59 - (void)removeCustomer:(id <SubscriptionCustomerProtocol>)customer withSubcriptionNumber:(NSString *)subscriptionNumber; 60 61 /** 62 发送消息到具体的订阅号中 63 64 @param message 消息 65 @param subscriptionNumber 订阅号码 66 */ 67 - (void)sendMessage:(id)message toSubscriptionNumber:(NSString *)subscriptionNumber; 68 69 /** 70 获取用户列表 71 72 @param subscriptionNumber 订阅号码 73 @return 返回用户列表 74 */ 75 - (NSHashTable *)existSubscriptionNumber:(NSString *)subscriptionNumber; 76 77 @end
SubscriptionServiceCenter.m
1 #import "SubscriptionServiceCenter.h" 2 3 static NSMutableDictionary *_subscriptionDictionary = nil; 4 5 @implementation SubscriptionServiceCenter 6 7 static SubscriptionServiceCenter *_instance = nil; 8 9 + (instancetype)shareInstance { 10 11 static dispatch_once_t onceToken; 12 dispatch_once(&onceToken, ^{ 13 _subscriptionDictionary = [NSMutableDictionary dictionary]; 14 _instance = [[super allocWithZone:NULL] init]; 15 }); 16 17 return _instance; 18 } 19 20 + (id)allocWithZone:(struct _NSZone *)zone { 21 22 return [SubscriptionServiceCenter shareInstance]; 23 } 24 25 - (id)copWithZone:(struct _NSZone *)zone { 26 27 return [SubscriptionServiceCenter shareInstance]; 28 } 29 30 - (void)createSubscriptionNumber:(NSString *)subscriptionNumber { 31 32 NSParameterAssert(subscriptionNumber); 33 34 NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber]; 35 if (hashTable == nil) { 36 37 hashTable = [NSHashTable weakObjectsHashTable]; 38 [_subscriptionDictionary setObject:hashTable forKey:subscriptionNumber]; 39 } 40 } 41 42 - (void)removeSubscriptionNUmber:(NSString *)subscriptionNumber { 43 44 NSParameterAssert(subscriptionNumber); 45 46 NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber]; 47 if (hashTable) { 48 49 [_subscriptionDictionary removeObjectForKey:subscriptionNumber]; 50 } 51 } 52 53 - (void)addCustomer:(id <SubscriptionCustomerProtocol>)customer withSubscriptionNumber:(NSString *)subscriptionNumber { 54 55 NSParameterAssert(customer); 56 NSParameterAssert(subscriptionNumber); 57 58 NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber]; 59 [hashTable addObject:customer]; 60 } 61 62 - (void)removeCustomer:(id <SubscriptionCustomerProtocol>)customer withSubcriptionNumber:(NSString *)subscriptionNumber { 63 64 NSParameterAssert(subscriptionNumber); 65 66 NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber]; 67 [hashTable removeObject:customer]; 68 } 69 70 - (void)sendMessage:(id)message toSubscriptionNumber:(NSString *)subscriptionNumber { 71 72 NSParameterAssert(subscriptionNumber); 73 74 NSHashTable *hashTable = [self existSubscriptionNumber:subscriptionNumber]; 75 if (hashTable) { 76 77 NSEnumerator *enumerator = [hashTable objectEnumerator]; 78 id <SubscriptionCustomerProtocol> object = nil; 79 while (object = [enumerator nextObject]) { 80 81 if ([object respondsToSelector:@selector(subscriptionMessage: subscriptionNumber:)]) { 82 83 [object subscriptionMessage:message subscriptionNumber:subscriptionNumber]; 84 } 85 } 86 } 87 } 88 89 - (NSHashTable *)existSubscriptionNumber:(NSString *)subscriptionNumber { 90 91 return [_subscriptionDictionary objectForKey:subscriptionNumber]; 92 } 93 94 @end
下面在Controller中实现, Controller作为用户即观察者
1 #import "ViewController.h" 2 #import "SubscriptionCustomerProtocol.h" 3 #import "SubscriptionServiceCenter.h" 4 5 static NSString * SCIENCE = @"SCIENCE"; 6 7 @interface ViewController () <SubscriptionCustomerProtocol> 8 9 @end 10 11 @implementation ViewController 12 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 16 //创建一个订阅服务中心单例 17 SubscriptionServiceCenter *center = [SubscriptionServiceCenter shareInstance]; 18 19 //创建一个订阅号 20 [center createSubscriptionNumber:SCIENCE]; 21 22 //添加一个用户 23 [center addCustomer:self withSubscriptionNumber:SCIENCE]; 24 25 //发送一个通知消息 26 [center sendMessage:@"有新的期刊啦" toSubscriptionNumber:SCIENCE]; 27 28 } 29 30 #pragma mark - SubscriptionCustomerProtocol 31 - (void)subscriptionMessage:(id)message subscriptionNumber:(NSString *)subscriptionNumber { 32 33 NSLog(@"期刊号: %@ 收到消息: %@", subscriptionNumber, message); 34 } 35 36 37 @end
Cocoa touch中的KVO和NSNotificationCenter的原理是观察模式的很好实现, 下面用代码分别演示下用法
KVO的用法
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 // Do any additional setup after loading the view, typically from a nib. 4 5 self.model = [Model new]; 6 7 //添加KVO 8 [self.model addObserver:self 9 forKeyPath:@"name" 10 options:NSKeyValueObservingOptionNew 11 context:nil]; 12 13 //发送信息, 通过修改属性 14 self.model.name = @"v1.0"; 15 16 } 17 18 #pragma mark - KVO方法 19 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { 20 NSLog(@"%@", change); 21 } 22 23 - (void)dealloc { 24 25 //移除KVO 26 [self.model removeObserver:self 27 forKeyPath:@"name"]; 28 }
NSNotificationCenter的用法
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 // Do any additional setup after loading the view, typically from a nib. 4 5 //添加 6 [[NSNotificationCenter defaultCenter] addObserver:self 7 selector:@selector(notificationCenterEvent:) 8 name:@"SCIENCE" 9 object:nil]; 10 11 //发送信息 12 [[NSNotificationCenter defaultCenter] postNotificationName:@"SCIENCE" 13 object:@"v1.0"]; 14 15 } 16 17 #pragma mark - 通知中心方法 18 - (void)notificationCenterEvent:(id)sender { 19 NSLog(@"%@", sender); 20 } 21 22 - (void)dealloc { 23 //移除通知中心 24 [[NSNotificationCenter defaultCenter] removeObserver:self 25 forKeyPath:@"SCIENCE"]; 26 27 }