開(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)題:
強(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方法。線程安全
我們可以看到核心類(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í)保證條件滿足。