代理可以一對(duì)多嗎? ---使用開(kāi)源庫(kù)要謹(jǐn)慎

開(kāi)源庫(kù)的使用我們需要注意其所屬協(xié)議,比如MIT、BSD等,注意這些協(xié)議不允許你做些什么。但這個(gè)不是本文重點(diǎn)。
本文結(jié)合一個(gè)多重代理的庫(kù)的解析和使用,來(lái)講一下使用開(kāi)源庫(kù)中使用部分代碼時(shí)的問(wèn)題。

我們都知道“協(xié)議” protocol可以用于對(duì)象之間的通信,并用于代碼的解耦,通常被我們用在相關(guān)性較強(qiáng)的對(duì)象之間。但是直接使用protocol delegate有一個(gè)缺陷,因?yàn)樗恢С忠粚?duì)多(多個(gè)對(duì)象同時(shí)作為一個(gè)對(duì)象A的代理, 當(dāng)A處理一個(gè)事件時(shí),這些對(duì)象都能接收到相關(guān)信息), 因?yàn)楫?dāng)我們?cè)O(shè)置了一個(gè)對(duì)象的.delegate時(shí), 再去設(shè)置下一個(gè)對(duì)象.delegate, 就會(huì)把之前的賦值覆蓋掉, 也就是說(shuō)同一時(shí)刻只會(huì)有一個(gè)代理在生效。這種情況下我們就只能使用通知 notification 嗎?

當(dāng)然不是!

引入

我們可以使用多方代理, 并且有車(chē)輪子! 即時(shí)通信開(kāi)源庫(kù)xmpp中就有一個(gè)類(lèi):GCDMulticastDelegate 可以實(shí)現(xiàn)多重代理。我們來(lái)分析下核心代碼(下面代碼有刪減,便于理解):

- (id)init
{
    if ((self = [super init]))
    {
        delegateNodes = [[NSMutableArray alloc] init];
    }
    return self;
}

首先GCDMulticastDelegate的初始化,先創(chuàng)建一個(gè)數(shù)組,后面講用于存放多重代理的對(duì)象信息,下面就是這個(gè)數(shù)組的add 和 remove方法:

- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    if (delegate == nil) return;
    if (delegateQueue == NULL) return;
    
    GCDMulticastDelegateNode *node =
        [[GCDMulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:delegateQueue];
    
    [delegateNodes addObject:node];
}

- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    if (delegate == nil) return;
    
    NSUInteger i;
    for (i = [delegateNodes count]; i > 0; i--)
    {
        GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:(i-1)];
        id nodeDelegate = node.delegate;
        if (delegate == nodeDelegate)
        {
            if ((delegateQueue == NULL) || (delegateQueue == node.delegateQueue))
            {
                node.delegate = nil;
                [delegateNodes removeObjectAtIndex:(i-1)];
            }
        }
    }
}

可以看出核心類(lèi)GCDMulticastDelegate中,傳入?yún)?shù)包含要添加代理的對(duì)象和代理回調(diào)時(shí)的線程,然后通過(guò)一個(gè)GCDMulticastDelegateNode的model來(lái)包含這兩個(gè)信息,然后將這個(gè)node對(duì)象存到數(shù)組delegateNodes中。也就是說(shuō)delegateNodes中存放的每個(gè)對(duì)象都包含兩個(gè)信息:要作為代理的對(duì)象和相關(guān)代理的方法調(diào)用時(shí)想要處于的線程。

@interface GCDMulticastDelegateNode : NSObject {

- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;

@property (/* atomic */ readwrite, unsafe_unretained) id delegate;
@property (nonatomic, readonly) dispatch_queue_t delegateQueue;

@end

基于上述信息我們可以基本知道如何使用:
...
// 初始化時(shí)
multicastDelegate = (GCDMulticastDelegate <MulticastDelegateBaseObjectDelegate>*)[[GCDMulticastDelegate alloc] init];
...
// 添加代理時(shí)
[multicastDelegate addDelegate:delegate delegateQueue:delegateQueue];
...
// 代理方法XXXX_Selector回調(diào)時(shí)
[multicastDelegate XXXX_Selector];

問(wèn)題來(lái)了:當(dāng)我們使用[multicastDelegate XXXX_Selector]; 時(shí),源碼內(nèi)部如何幫我處理的呢?

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    for (GCDMulticastDelegateNode *node in delegateNodes)
    {
        id nodeDelegate = node.delegate;
        NSMethodSignature *result = [nodeDelegate methodSignatureForSelector:aSelector];
        
        if (result != nil)
        {
            return result;
        }
    }
    
    return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}

- (void)forwardInvocation:(NSInvocation *)origInvocation
{
    SEL selector = [origInvocation selector];
    BOOL foundNilDelegate = NO;
    
    for (GCDMulticastDelegateNode *node in delegateNodes)
    {
        id nodeDelegate = node.delegate;
        if ([nodeDelegate respondsToSelector:selector])
        {
            NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
            dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
                [dupInvocation invokeWithTarget:nodeDelegate];
            }});
        }
        else if (nodeDelegate == nil)
        {
            foundNilDelegate = YES;
        }
    }
    
    if (foundNilDelegate)
    {
        NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
        NSUInteger i = 0;
        for (GCDMulticastDelegateNode *node in delegateNodes)
        {
            id nodeDelegate = node.delegate;
            if (nodeDelegate == nil)
            {
                [indexSet addIndex:i];
            }
            i++;
        }
        
        [delegateNodes removeObjectsAtIndexes:indexSet];
    }
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    // Prevent NSInvalidArgumentException
}

可以看到其中利用了消息轉(zhuǎn)發(fā)的原理,重寫(xiě)了methodSignatureForSelector:和forwardInvocation:兩個(gè)方法。

methodSignatureForSelector:的作用在于為另一個(gè)類(lèi)實(shí)現(xiàn)的消息創(chuàng)建一個(gè)有效的方法簽名,必須實(shí)現(xiàn),并且返回不為空的methodSignature,否則會(huì)crash。methodSignatureForSelector在找到相應(yīng)方法的簽名時(shí),如果找到了就直接返回,如果找不到就返回 donothing的簽名,這個(gè)donothing肯定是沒(méi)有實(shí)現(xiàn)的,所以此時(shí)Nsobject就會(huì)調(diào)用doesNotRecognizeSelector,這樣就可以避免crash。

我們知道OC中的方法調(diào)用都通過(guò)消息發(fā)送,消息經(jīng)過(guò)轉(zhuǎn)發(fā)時(shí),都要調(diào)用forwardInvocation:,所以在forwardInvocation中遍歷delegateNodes的每個(gè)node對(duì)象,根據(jù)node的代理對(duì)象和線程信息,在指定的線程中使用這個(gè)對(duì)象調(diào)用相應(yīng)協(xié)議的方法。

以上就是這個(gè)類(lèi)的核心思想,其他幾個(gè)方法這里不做介紹。

注意 !

注意直接使用這個(gè)類(lèi)有兩個(gè)重要問(wèn)題:

  1. 強(qiáng)引用
    我們可以看到用于保存代理對(duì)象的容器是NSMutableArray類(lèi)型delegateNodes,delegateNodes將強(qiáng)引用這些對(duì)象,如果我們沒(méi)有在必要的時(shí)候?qū)⑵鋸膁elegateNodes中刪除,那么將一直引用著對(duì)象,導(dǎo)致對(duì)象不能釋放,增加內(nèi)存使用量,并存在著內(nèi)存泄漏的風(fēng)險(xiǎn)。 所以解決這個(gè)問(wèn)題的關(guān)鍵就是我們需要在合適的位置調(diào)用removeDelegate方法。

  2. 線程安全
    我們可以看到核心類(lèi)的方法中,多個(gè)方法中存在著對(duì)NSMutableArray類(lèi)型delegateNodes的遍歷、增加、刪除,那么當(dāng)處于多線程的環(huán)境中,這些方法如果恰好在不同線程中調(diào)用,例如一個(gè)線程正在調(diào)用removeDelegate , 另一個(gè)線程正在遍歷delegateNodes,就會(huì)產(chǎn)生# <__NSArrayM: 0xb550c30> was mutated while being enumerated. 這種類(lèi)型的crash。

我們有兩種方式可以解決:
第一種方法,可以仿照xmpp中調(diào)用GCDMulticastDelegate的形式,將每個(gè)接口的調(diào)用都協(xié)調(diào)到一個(gè)固定的線程中,將這些接口再封裝一層接口,使我們調(diào)用接口時(shí)已經(jīng)做好線程安全,實(shí)例代碼如下:

@implementation MulticastDelegateObject

- (id)init
{
    return [self initWithDispatchQueue:NULL];
}

- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
    if ((self = [super init]))
    {
        if (queue)
        {
            moduleQueue = queue;
        }
        else
        {
            const char *moduleQueueName = [NSStringFromClass([self class]) UTF8String];
            moduleQueue = dispatch_queue_create(moduleQueueName, NULL);
        }
        
        moduleQueueTag = &moduleQueueTag;
        dispatch_queue_set_specific(moduleQueue, moduleQueueTag, moduleQueueTag, NULL);
        multicastDelegate = (GCDMulticastDelegate <MulticastDelegateBaseObjectDelegate>*)[[GCDMulticastDelegate alloc] init];
    }
    return self;
}

- (dispatch_queue_t)moduleQueue
{
    return moduleQueue;
}

- (void *)moduleQueueTag
{
    return moduleQueueTag;
}

- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    dispatch_block_t block = ^{
        [multicastDelegate addDelegate:delegate delegateQueue:delegateQueue];
    };
    
    if (dispatch_get_specific(moduleQueueTag))
        block();
    else
        dispatch_async(moduleQueue, block);
}

- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue synchronously:(BOOL)synchronously
{
    dispatch_block_t block = ^{
        [multicastDelegate removeDelegate:delegate delegateQueue:delegateQueue];
    };
    
    if (dispatch_get_specific(moduleQueueTag))
        block();
    else if (synchronously)
        dispatch_sync(moduleQueue, block);
    else
        dispatch_async(moduleQueue, block);
}

@end

第二個(gè)方法,使用線程保護(hù)的方式對(duì)GCDMulticastDelegate中每個(gè)方法中對(duì)delegateNodes有操作的代碼段,都進(jìn)行保護(hù)起來(lái),我們這里以信號(hào)量的形式來(lái)舉例說(shuō)明:

- (id)init
{
    if ((self = [super init]))
    {
        delegateNodes = [[NSMutableArray alloc] init];
        
        signal = dispatch_semaphore_create(1);
    }
    return self;
}
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    if (delegate == nil) return;
    if (delegateQueue == NULL) delegateQueue = dispatch_get_main_queue();
    
    GCDMulticastDelegateNode *node =
        [[GCDMulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:delegateQueue];
    
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    [delegateNodes addObject:node];
    dispatch_semaphore_signal(signal);
}

- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    if (delegate == nil) return;
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    NSUInteger i;
    for (i = [delegateNodes count]; i > 0; i--)
    {
        GCDMulticastDelegateNode *node = [delegateNodes objectAtIndex:(i-1)];   
        id nodeDelegate = node.delegate;
        if (delegate == nodeDelegate)
        {
            if ((delegateQueue == NULL) || (delegateQueue == node.delegateQueue))
            {
                node.delegate = nil;
                [delegateNodes removeObjectAtIndex:(i-1)];
            }
        }
    }
    dispatch_semaphore_signal(signal);
}

我們首先在實(shí)例化方法中,初始化了信號(hào)量和超時(shí)時(shí)間,然后在每個(gè)對(duì)delegateNodes有操作的方法相關(guān)代碼段 的前后,使用dispatch_semaphore_wait 和 dispatch_semaphore_signal這對(duì)基友來(lái)保證所有處理都要先獲得信號(hào)量,這樣就保證對(duì)delegateNodes的操作都串行進(jìn)行,即保證了線程安全。

p.s.
當(dāng)然這兩個(gè)問(wèn)題并非這個(gè)源碼本身的問(wèn)題,因?yàn)檫@個(gè)類(lèi)本身并非一個(gè)獨(dú)立的庫(kù)開(kāi)源,而是作為xmpp內(nèi)部使用(內(nèi)部合理的使用不會(huì)出現(xiàn)上述問(wèn)題),我們自己將其抽離出來(lái)使用時(shí),就需要根據(jù)自己的需求合理調(diào)用接口,才能規(guī)避問(wèn)題。

總結(jié)

綜上,我們使用開(kāi)源代碼時(shí),如果只使用其中一部分,要充分理解這部分代碼的使用環(huán)境,如果需要在一些條件的前提下使用才可以,那么就需要看是否能在使用時(shí)保證條件滿足。

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

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

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