前段時間在研究XMPPFramework的時候發(fā)現(xiàn)了里面一個很有趣的特性,MulticastDelegate,也就是多重代理。我們知道iOS開發(fā)中對象直接常用的溝通方式一般分為Block,Delegate 和 Notification 其中只有Notification是一對多的,而Delegate 和 Block都是一對一的。XMPPFramework中對于 MulticastDelegate的解釋如下:
The xmpp framework needs to support an unlimited number of extensions. This includes the official extensions that ship with the framework, as well as any number of extensions or custom code you may want to plug into the framework. So the traditional delegate pattern simply won't work. XMPP modules and extensions need to be separated into their own separate classes, yet each of these classes needs to receive delegate methods. And the standard NSNotification architecture won't work either because some of these delegates require a return variable. (Plus it's really annoying to extract parameters from a notification's userInfo dictionary.)
簡單來說就是XMPPFramework需要支持很多的擴(kuò)展功能,而且這些擴(kuò)展功能是可以共存的,XMPPFramework 的 Module 需要區(qū)分這些擴(kuò)展,同時這些擴(kuò)展的代理需要一個返回值,所以也不能用通知,最終使用了多重代理。
XMPPFramework怎么說
XMPPFramework中對MulticastDelegate的具體說明分為了以下幾點(diǎn)
delegates and notifications
主要說明了代理和通知各自的利弊
| 代理 | 通知
-------------|-------------|-------------
優(yōu)點(diǎn) | 大量回調(diào)方法是書寫方便</p>回調(diào)參數(shù)讀寫方便</p>允許返回值| 允許一對多傳值
缺點(diǎn) | 只能一對一傳值</p> | 回調(diào)較多時注冊繁瑣</p> 從userInfo中去除參數(shù)繁瑣 </p> 不允許返回值
What are the requirements for XMPPFramework?
1.XMPPFramework需要一對多廣播消息的的能力
2.方便擴(kuò)展
3.允許返回值
4.線程安全(Socket IO, xml解析, disk IO等)
What's it look like?
客戶端使用MulticastDelegate的方式如下
// 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)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
...
}
What about return variables?
我們有如下代理方法
- (BOOL)worker:(Worker *)sender shouldPerformSubTask:(id)subtask;
如果有三個對象都實(shí)現(xiàn)了這個代理,其中兩個返回 YES,一個返回 NO,我們應(yīng)該根據(jù)不同的情況做不同處理,比如在這里,我們處理為只要有一個代理返回NO,那么我們就不執(zhí)行subtask。
那么問題就來了:我們該如何實(shí)現(xiàn)這種代理邏輯呢?
GCDMulticastDelegate中的每一個節(jié)點(diǎn)都保存有一個delegate和一個dispatch_queue,但是我們并不能只是簡單的遍歷GCDMulticastDelegate中的節(jié)點(diǎn),原因如下:
假設(shè)我們的對象運(yùn)行在
dispatch_queue_a, delegate運(yùn)行在dispatch_queue_b,而我們使用dispatch_sync(dispatch_queue_b, block),同時在block中使用了當(dāng)前對象的一些屬性,那么就會形成死鎖。
處理返回值的 MulticastDelegate 實(shí)現(xiàn)舉例:
// Delegate rules:
//
// 如果有任意一個代理返回NO,則result為NO
// 否則result為YES.
SEL selector = @selector(worker:shouldPerformSubTask:);
NSUInteger delegateCount = [multicastDelegate countForSelector:selector];
if (delegateCount == 0)
{
// 沒有代理實(shí)現(xiàn)該方法的時候默認(rèn)為YES
[self continuePerformSubTask:YES];
}
else
{
// 查詢代理
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 {
// 等待代理執(zhí)行結(jié)束
dispatch_group_wait(delGroup, DISPATCH_TIME_FOREVER);
BOOL shouldPerformSubTask = (dispatch_semaphore_wait(delSemaphore, DISPATCH_TIME_NOW) != 0);
dispatch_async(ourQueue, ^{ @autoreleasepool {
[self continuePerformSubTask:shouldPerformSubTask];
}});
dispatch_release(delSemaphore);
dispatch_release(delGroup);
}});
}
GCDMulticastDelegate
上面示例中提到了GCDMulticastDelegate,也就是多重代理的核心實(shí)現(xiàn)類, XMPPFramework的源碼中可以看到GCDMulticastDelegate包含分為以下幾部分:
- GCDMulticastDelegateNode
- GCDMulticastDelegateEnumerator
- GCDMulticastDelegate
GCDMulticastDelegateNode表示代理數(shù)組中的節(jié)點(diǎn),里面包括一個代理對象 id delegate 和一個執(zhí)行代理需要的現(xiàn)成 dispatch_queue_t delegateQueue。
GCDMulticastDelegateEnumerator,用于對當(dāng)前的代理數(shù)組進(jìn)行枚舉操作,屬性有 代理數(shù)組 delegateNodes, 當(dāng)前遍歷的節(jié)點(diǎn)下標(biāo)currentNodeIndex,代理數(shù)組總數(shù) numNodes
GCDMulticastDelegate 主要實(shí)現(xiàn)了對于代理數(shù)組的增刪操作已經(jīng)特殊的統(tǒng)計(jì)操作如:
//返回某個各項(xiàng)遵循代理的數(shù)量
- (NSUInteger)countOfClass:(Class)aClass
{
NSUInteger count = 0;
for (GCDMulticastDelegateNode *node in delegateNodes)
{
id nodeDelegate = node.delegate;
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
if (nodeDelegate == [NSNull null])
nodeDelegate = node.unsafeDelegate;
#endif
if ([nodeDelegate isKindOfClass:aClass])
{
count++;
}
}
return count;
}
//返回實(shí)現(xiàn)了某個選擇子的代理的數(shù)量
- (NSUInteger)countForSelector:(SEL)aSelector
{
NSUInteger count = 0;
for (GCDMulticastDelegateNode *node in delegateNodes)
{
id nodeDelegate = node.delegate;
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
if (nodeDelegate == [NSNull null])
nodeDelegate = node.unsafeDelegate;
#endif
if ([nodeDelegate respondsToSelector:aSelector])
{
count++;
}
}
return count;
}
綜上所述,當(dāng)我們遇到對象一對多傳值,同時需要一個返回值的時候就可以使用MulticastDelegate。