代理模式在Cocoa编程中使用非常普遍。代理模式通常是一对一的关系,一个事件只能通过代理方法通知给一个另外的对象。
想象一下A用户跟B用户聊天应用的场景:A接收到B用户的新消息,首先要将消息显示在聊天窗口中,其次要更新联系人列表将最新消息显示在对应的用户的列表中。
假设联系人列表的Controller是BuddyListController,聊天窗口的Controller是ChatViewController。
消息接收由MessageManager统一管理。如果说收到聊天消息是一个事件的话,对于每个聊天消息,都需要MessageManager将这个消息事件同时通知到2个Controller(BuddyListController和ChatViewController),由各自Controller完成页面的更新。
1.代理
如果使用代理来实现消息通知,首先需要定义代理协议接口
@protocol MessageDelegate
@optional
-(void)recieveMessage:(Message *)message;
@end
MessageManager中定义2个代理属性delegate1,delegate2,本别对应BuddyListController,ChatViewController。
@interface MessageManager : NSObject
@property(nonatomic,weak) id<MessageDelegate> delegate1;
@property(nonatomic,weak) id<MessageDelegate> delegate2;
@end
这样当MessageManager接受到消息时,可以通过如下方式通知到不同的Controller
if ([self.delegate1 respondsToSelector:@selector(recieveMessage:)]){
....
}
if ([self.delegate2 respondsToSelector:@selector(recieveMessage:)]){
....
}
2.通知
通知支持实现一对多的事件处理。
定义消息名称kChatMessage,BuddyListController 和 ChatViewController 都可以注册为聊天消息kChatMessage事件的接收者。当有消息到达了,MessageManager发送kChatMessage消息事件,BuddyListController 和 ChatViewController 接收处理即可。
[[NSNotificationCenter defaultCenter] addObserverForName:kChatMessage object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* note){
Message *message = note.object ;
}];
3.多代理
代理只适合一对一关系的处理,要处理一对多的场景需要添加多个代理属性,代码结构繁琐不够清晰。
通知支持一对多关心的处理,每次处理都需要从消息通知类NSNotification中拆解数据,不支持调用需要有返回值的场景。通知是全局的,任何类都可以注册接收消息。
多代理融合代理和通知的优点,支持
1)一对多处理,
2)执行代理方法同时可以获取返回值
3)可以控制事件的接收范围
既然是多代理,首先考虑的用数组delegateNodes将多个代理存储起来,然后当事件发生的时候,遍历数组对各个代理对象进行方法回调通知。
基本的伪代码如下:
- (void)recieveMessage:(Message *)message{
for(id delegate in self.delegateNodes){
if ([delegate respondsToSelector:@selector(recieveMessage:sender:)]){
[delegate recieveMessage:message sender:self];
}
}
}
如果代理协议定义了多个方法,则对每个代理方法都要写上述类似的代码。利用oc中runtime提供的消息转发机制可以实现在对象A上执行对象B的方法,因此我们可以考虑使用消息转发机制来简化代码实现。
使用NSMethodSignature和NSInvocation实现消息转发。
实现消息转发,必须实现两个方法: methodSignatureForSelector 和 forwardInvocation。
methodSignatureForSelector的作用在于为另一个类实现的消息创建一个有效的方法签名;forwardInvocation:将方法调用转发给一个真正实现了该消息的对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
for (id delegate in self.delegateNodes){ NSMethodSignature *result = [delegate methodSignatureForSelector:aSelector];
if (result != nil){
return result;
}
}
}
- (void)forwardInvocation:(NSInvocation *)origInvocation{
SEL selector = [origInvocation selector];
for (id delegate in self.delegateNodes){
if ([delegate respondsToSelector:selector]){
[origInvocation invokeWithTarget:delegate];
}
}
}
这样我们就可以方便的在多代理的管理类上面简单的执行代理方法,而不需要循环遍历对每个代理执行一遍。
[multicastDelegate recieveMessage:message sender:self];
我们来看看单代理的方法调用
if ([delegate respondsToSelector:@selector(recieveMessage:sender:)]){
[delegate recieveMessage:message sender:self];
}
可以看出多代理的调用方法和参数跟单代理基本是一样的,而实际上是multicastDelegate多代理管理类内部又做了一次透明的多个代理转发。
第三方开源的XMPPFramework XMPPFramework 框架中实现了一个通用的多代理类GCDMulticastDelegate,我们的例子基于GCDMulticastDelegate来实现。
GCDMulticastDelegate中增加删除代理的接口定义
@interface GCDMulticastDelegate : NSObject
-(void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
-(void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
@end
代理存储数组变量delegateNodes
@interface GCDMulticastDelegate () {
NSMutableArray *delegateNodes;
}
同时实现了methodSignatureForSelector 和 forwardInvocation 2个用于消息转发的方法。
(具体实现请参考XMPPFramework源代码)
我们来实现一个简单的联系人列表界面如下:
左边是联系人列表,每一行显示用户昵称和是否在线的状态,如果在线为绿色小圆,不在线的话为灰色。
当点击左边每一行时右边页面会显示联系人的详细资料。
当用户上线或者下线时,左边的状态小圆要实时更新。同时左边页面上的头像也要更新,如果离线的话头像需要灰色显示。
新建一个工程BuddyMultiDelegateDemo。
创建3个Controller:
MainViewController.m实现
#import "MainViewController.h"
#import "BuddyListViewController.h"
#import "BuddyDetailViewController.h"
#import "Masonry.h"
@interface MainViewController ()
@property(nonatomic,strong)BuddyListViewController *buddyListViewController;
@property(nonatomic,strong)BuddyDetailViewController *buddyDetailViewController;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSSplitViewItem *item1 = [[NSSplitViewItem alloc]init];
item1.viewController = self.buddyListViewController;
item1.canCollapse =YES;
[item1 setHoldingPriority:250];
NSSplitViewItem *item2 = [[NSSplitViewItem alloc]init];
item2.viewController = self.buddyDetailViewController;
item2.canCollapse = YES;
[item2 setHoldingPriority:270];
//水平方向
self.splitView.vertical = YES;
//增加NSSplitViewItem对象
[self addSplitViewItem:item1];
[self addSplitViewItem:item2];
//设置第一个视图的宽度最小100,最大200
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.greaterThanOrEqualTo(@700);
make.height.greaterThanOrEqualTo(@260);
}];
//设置第一个视图的宽度最小100,最大200
[item1.viewController.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.greaterThanOrEqualTo(@100);
make.width.lessThanOrEqualTo(@200);
}];
//设置第二个视图的宽度最小100,最大800
[item2.viewController.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.greaterThanOrEqualTo(@100);
make.width.lessThanOrEqualTo(@600);
}];
// Do view setup here.
}
- (BuddyListViewController*)buddyListViewController{
if(!_buddyListViewController){
_buddyListViewController = [[BuddyListViewController alloc]init];
}
return _buddyListViewController;
}
- (BuddyDetailViewController*)buddyDetailViewController{
if(!_buddyDetailViewController){
_buddyDetailViewController = [[BuddyDetailViewController alloc]init];
}
return _buddyDetailViewController;
}
@end
Buddy:联系人基本信息类
#import "DBModel.h"
@interface Buddy : DBModel
@property (nonatomic, assign) NSInteger ID;
@property (nonatomic, strong) NSString *nickName;
@property (nonatomic, assign) BOOL status;
/*init property member var by parsing NSDictionary parameter*/
-(id)initWithDictionary:(NSDictionary *)dictionary;
@end
BuddyDetail: 联系人详细信息类
#import "DBModel.h"
@interface BuddyDetail : DBModel
@property (nonatomic, assign) NSInteger ID;
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) NSString *birthDay;
@property (nonatomic, strong) NSString *image;
/*init property member var by parsing NSDictionary parameter*/
- (id)initWithDictionary:(NSDictionary *)dictionary;
/*save new model object*/
@end
多代理协议定义类BuddyStatusMulticastDelegate
1.定义了BuddyStatusDelegate协议,申明了2个代理方法。
2.实现代理方法对应的广播方法:具体是使用multicastDelegate广播代理方法
BuddyStatusMulticastDelegate.h
#import "XXXMultiDelegateModule.h"
#import "GCDMulticastDelegate.h"
@class Buddy;
@interface BuddyStatusMulticastDelegate : XXXMultiDelegateModule
-(void)buddyOfflineRequest:(Buddy *)buddy;
-(void)buddyOnlineRequest:(Buddy *)buddy;
@end
@protocol BuddyStatusDelegate
@optional
-(void)buddyStatus:(BuddyStatusMulticastDelegate *)sender didReceiveBuddyOfflineRequest:(Buddy *)buddy;
-(void)buddyStatus:(BuddyStatusMulticastDelegate *)sender didReceiveBuddyOnlineRequest:(Buddy *)buddy;
@end
BuddyStatusMulticastDelegate.m
#import "BuddyStatusMulticastDelegate.h"
@implementation BuddyStatusMulticastDelegate
- (void)buddyOfflineRequest:(Buddy *)buddy{
[multicastDelegate buddyStatus:self didReceiveBuddyOfflineRequest:buddy];
}
- (void)buddyOnlineRequest:(Buddy *)buddy{
[multicastDelegate buddyStatus:self didReceiveBuddyOnlineRequest:buddy];
}
@end
多代理协议管理类BuddyStatusManager
#import "BuddyStatusMulticastDelegate.h"
@interface BuddyStatusManager : NSObject
+(instancetype)sharedInstance;
@property(nonatomic,readonly)BuddyStatusMulticastDelegate *statusDelegate;
@end
#import "BuddyStatusManager.h"
#import "BuddyStatusMulticastDelegate.h"
@interface BuddyStatusManager()
@property(nonatomic,readwrite)BuddyStatusMulticastDelegate *statusDelegate;
@end
@implementation BuddyStatusManager
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
static BuddyStatusManager *sharedInstance;
dispatch_once(&onceToken, ^{
sharedInstance = [[BuddyStatusManager alloc] init];
});
return sharedInstance;
}
- (BuddyStatusMulticastDelegate*)statusDelegate{
if(!_statusDelegate){
_statusDelegate = [[BuddyStatusMulticastDelegate alloc]init];
}
return _statusDelegate;
}
@end
在BuddyListViewController 的 viewDidLoad 和 dealloc中分别增加和删除多代理。
- (void)viewDidLoad{
[super viewDidLoad];
[self addBuddyMultiDelegate];
}
- (void)dealloc{
[self removeBuddyMultiDelegate];
}
#pragma maek - multiDelegate
- (void)addBuddyMultiDelegate {
[[BuddyStatusManager sharedInstance].statusDelegate addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)removeBuddyMultiDelegate {
[[BuddyStatusManager sharedInstance].statusDelegate removeDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (void)buddyStatus:(BuddyStatusMulticastDelegate *)sender didReceiveBuddyOfflineRequest:(Buddy *)buddy {
[self upDateBuddyStatus:buddy];
}
- (void)buddyStatus:(BuddyStatusMulticastDelegate *)sender didReceiveBuddyOnlineRequest:(Buddy *)buddy {
[self upDateBuddyStatus:buddy];
}
- (void)upDateBuddyStatus:(Buddy *)buddy {
for(Buddy *aBubby in self.buddies) {
if(aBubby.ID==buddy.ID){
aBubby.status = buddy.status;
break;
}
}
[self.tableView reloadData];
}
在BuddyDetailViewController中使用也是类似,不再赘述。
我们在AppDelegate中实现一个测试方法,模拟用户在线状态变化,这样在列表BuddyListViewController 和 详细资料窗口BuddyDetailViewController 中都能收到用户状态变化的代理通知。
- (void)buddyTest {
NSInteger ID = random() % 2 + 1;
NSInteger status = random() % 10;
Buddy *buddy = [[Buddy alloc]init];
buddy.ID = ID;
buddy.status = NO;
if(status>5){
buddy.status = YES;
}
if(!buddy.status){
[[BuddyStatusManager sharedInstance].statusDelegate buddyOfflineRequest:buddy];
}
else{
[[BuddyStatusManager sharedInstance].statusDelegate buddyOfflineRequest:buddy];
}
NSLog(@"ID %ld status %d ",ID,buddy.status);
}