该文档回答了这几个问题:(1)什么是多重委托?(2)使用的原因。(3)为什么不使用普通的委托与通知?

介绍


苹果有两种常用的回调方式:

  • 委托
  • 通知
    委托非常简单和直接,用户注册自己为一个委托,然后实现它需要去实现的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
[worker setDelegate:self];

-(void)wokerDidFinish:(Worker *)sender
{
}

-(void)woker:(Worker *)sender didFinishSubTask:(id)subtask inDuration:(NsTimeInterval)elapsed
{
}

-(BOOL)worker:(Worker *)sender shouldPerformSubTask:(id)subtask
{
}

通知同样简单,但是需要多了几步,用户需要分别注册它们感兴趣的每个通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(workerDidFinish:)
name:WorkerDidFinishNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(workerDidFinishSubTask:)
name:WorkerDidFinishSubTaskNotification
object:nil];

- (void)workerDidFinish:(NSNotification *)notification
{
Worker *sender = [notification object];
}

- (void)workerDidFinishSubTask:(NSNotification *)notification
{
Worker *sender = [notification object];
id subtask = [[notification userInfo] objectForKey:@"subtask"];
NSTimeInterval elapsed = [[[notification userInfo] objectForKey"duration"] doubleValue];
}

其中我们要将参数从通知字典中取出,也就是说用户需要知道正确的键值才能取出对应的参数。
另外第三方委托方法不能通过通知来实现,因为通知不允许返回变量
Delegate的缺点:

  • 只能有一个委托
    notification的缺点:
  • 注册多个回调时比较麻烦
  • 从字典中取出参数非常麻烦
  • 当需要返回值时无法使用

    XMPP框架需要哪些东西?


  1. xmpp框架必须能够把事件广播给多个接收者。
    以消息为例,可能存在多个接收端:聊天窗口,历史日志以及消息推送系统。
  2. xmpp框架必须易于扩展。
    它必须能够支持大量的EXP’s以及任何想进行二次开发的xmpp协议的开发者,我们选择的解决方案必须在传播端和接收端都易于使用。
  3. 必须支持返回值。
    一个不错的例子是XMPP RFC的IQ处理授权。如果一个客户端接收到一个类型为’get’或’set’的IQ,但是不知道该怎么处理时,它必须返回一个类型为’error’的IQ,这在多插件时非常有用。
  4. 必须有效维持线程安全。
    xmpp框架存在大量并行,Socket接口、xml解析、xmpp、模块、磁盘接口以及委托都能够运行在他们自己的GCD队列中,这在多核设备中意味着许多任务同时运行在不同的线程中,系统不应该通过跳出循环来维持线程安全。

从这些看来委托和通知都不能很好的满足我们的要求,所以我们设计了GCDMulticastDelegate类。

如何运作


它非常简单,作为客户端,你只要这样:

1
2
3
4
5
6
7
8
//Add myself as a delegate, and tell xmppStream to invoke my delegate methods on the main thread
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
//
//Then just implement whatever delegate methods you need like normal
-(void)smppStream:(XMPPStream *)sender didReciveMessage:(XMPPMessage *)message
{
...
}

就是这些,你可以看到,它和普通的委托非常相似,但是允许你指定特定线程。
如果之后你决定把你的操作移出主线程的话,同样非常简单:

1
2
3
4
//Handle most stuff on the main thread
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
//But do that one slow thing on a background queue so it doesn't slow down the UI anymore
[xmppSream addDelegate:bgProcessor delegateQueue:bgProcessorQueue];

在IPhone这样的设备环境下,这样做可以很好的维持你应用的性能。

如何在自己的插件中使用


为了使用多重委托,作为广播端,你需要申明并初始化它:
GCDMulticastDelegate multicastDelegate;
multicastDelegate = (GCDMulticastDelegate
)[[GCDMulticastDelegate alloc] init];
然后添加能够允许其他对象从委托列表中添加/删除委托的方法:

1
2
3
4
5
6
7
8
9
-(void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
[multicastDelegate addDelegate:delegate delegateQueue:delegateQueue];
}

-(void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
[multicastDelegate removeDelegate:delelgate delegateQueue:delegateQueue];
}

当你要触发某个委托方法时,只要是注册了的委托,你可以:
[multicastDelegate worker:self didFinishSubTask:subtask inDyration:elapsed];

关于返回变量


对于下面的这个委托方法:
-(BOOL)worker:(Worker *)sender shouldPerformSubTask:(id)subtask;

假如返回了三个委托,两个返回YES另一个返回NO,那如何处理它?
一般来说,如果任何一个委托返回NO,那我们就不能执行这个任务。
那么如何实现它?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Delegate rules:
//
// If ANY of the delegates return NO, then the result is NO.
// Otherwise the result is YES.

SEL selector = @selector(worker:shouldPerformSubTask:);

NSUInteger delegateCount = [multicastDelegate countForSelector:selector];
if (delegateCount == 0)
{
// No delegates implement the selector - default is YES
[self continuePerformSubTask:YES];
}
else
{
// Query the delegate(s)
GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];

dispatch_semaphore_t delSemaphore = dispatch_semaphore_create(0);
dispatch_group_t delGroup = dispatch_group_create();

id del;
dispatch_queue_t dq;

while ([delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
{
dispatch_group_async(delGroup, dq, ^{ @autoreleasepool {

if (![del worker:self shouldPerformSubTask:subtask])
{
dispatch_semaphore_signal(delSemaphore);
}
}});
}

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{ @autoreleasepool {

// Wait for the delegates to finish
dispatch_group_wait(delGroup, DISPATCH_TIME_FOREVER);

// What was the delegate response?
BOOL shouldPerformSubTask = (dispatch_semaphore_wait(delSemaphore, DISPATCH_TIME_NOW) != 0);

dispatch_async(ourQueue, ^{ @autoreleasepool {
[self continuePerformSubTask:shouldPerformSubTask];
}});

dispatch_release(delSemaphore);
dispatch_release(delGroup);
}});
}