iOS 之 MulticastDelegate

前段時間在研究XMPPFramework的時候發(fā)現(xiàn)了里面一個很有趣的特性,MulticastDelegate,也就是多重代理。我們知道iOS開發(fā)中對象直接常用的溝通方式一般分為BlockDelegateNotification 其中只有Notification是一對多的,而DelegateBlock都是一對一的。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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,627評論 30 472
  • 史上最全的iOS面試題及答案 iOS面試小貼士———————————————回答好下面的足夠了----------...
    Style_偉閱讀 2,576評論 0 35
  • iOS面試小貼士 ———————————————回答好下面的足夠了------------------------...
    不言不愛閱讀 2,253評論 0 7
  • 多線程、特別是NSOperation 和 GCD 的內(nèi)部原理。運(yùn)行時機(jī)制的原理和運(yùn)用場景。SDWebImage的原...
    LZM輪回閱讀 2,124評論 0 12
  • __block和__weak修飾符的區(qū)別其實(shí)是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,598評論 0 6

友情鏈接更多精彩內(nèi)容