OC 如何實現(xiàn)多代理模式
為什么要使用多代理模式
標題雖然是如何實現(xiàn)多代理模式蓄诽,但是知道為什么需要實現(xiàn)多代理模式同樣重要鸭巴。
眾所周知,OC的常用的消息傳遞方式有很多種,各有各的好處京郑,在不同的場景選擇不同實現(xiàn)方式宅广。如:
代理 1對1,高耦合
通知 1對多些举,松耦合
block
KVO
...
不同的實現(xiàn)方式有不同的應用場景跟狱,也有各自的優(yōu)缺點。普通的代理模式只能應用與1對1的場景金拒,針對1對多的場景只能被迫選擇使用通知兽肤。
但是通知也有自己的缺點:
在編譯期間不會檢查通知是否能夠被觀察者正確的處理;
在釋放通知的觀察者時绪抛,需要在通知中心移除觀察者资铡;
在調(diào)試的時候,通知傳遞的過程很難控制和跟蹤幢码;
發(fā)送通知和接收通知時需要提前知道通知名稱笤休,如果通知名稱不一致,會出現(xiàn)不同步的情況症副;
通知發(fā)出后店雅,不能從觀察者獲得任何的反饋信息。對于需要返回值的場景沒有辦法處理贞铣。
如果代理模式能支持多個響應對象闹啦,那么就不會再有以上的問題。
如何實現(xiàn)多代理模式
單代理模式
一個最普通的代理模式如下:
代理協(xié)議ReportDelegate:
@protocol ReportDelegate <NSObject>
//上交報告
- (void)report;
@end
類ComandA辕坝,發(fā)送命令者
#import "ReportDelegate.h"
@interface ComandA : NSObject
@property (weak, nonatomic) id <ReportDelegate> delegate;
/**
發(fā)送上交報告的命令
*/
- (void)sendOrder;
@end
@implementation ComandA
- (void)sendOrder
{
if(self.delegate && [self.delegate respondsToSelector:@selector(report)])
{
[self.delegate report];
}
}
@end
類ExecutorB窍奋,執(zhí)行命令者
#import "ReportDelegate.h"
@interface ExecutorB : NSObject <ReportDelegate>
@end
@implementation ExecutorB
- (void)report
{
NSLog(@"我要上交報告");
}
@end
現(xiàn)在一個ComandA
對象A可以命令一個ExecutorB
對象B上交報告。因為ComandA
只定義了一個單成員@property (weak, nonatomic) id <ReportDelegate> delegate;
酱畅。
最初的多代理模式
如果現(xiàn)在將delegate
變?yōu)?code>id <ReportDelegate> delegate的數(shù)組delegates
琳袄。在sendOrder
方法中遍歷delegates
數(shù)組去調(diào)用每個delegate
執(zhí)行代理方法不就好了。類似下面:
@interface ComandA : NSObject
@property (strong, nonatomic) NSPointerArray *delegates;
/**
發(fā)送上交報告的命令
*/
- (void)sendOrder;
@end
@implementation ComandA2
- (void)sendOrder
{
for (NSUInteger i = 0; i < self.delegates.count; i += 1) {
id delegate = (__bridge id)[self.delegates pointerAtIndex:i];
if(delegate && [delegate respondsToSelector:@selector(report)])
{
[delegate report];
}
}
}
@end
多代理就這樣實現(xiàn)了纺酸,現(xiàn)在一個ComandA
對象A可以命令多個ExecutorB
對象B上交報告窖逗,只要提前將多個ExecutorB
對象加入到delegates
數(shù)組中即可。 之所以選擇NSPointerArray
餐蔬,是因為NSPointerArray
不增加成員的引用計數(shù)碎紊,相當于弱引用,在釋放一個delegate
前樊诺,就算不將其從delegates
數(shù)組中移除也不會有問題仗考。
一切看起來非常完美,對的啄骇,只是看起來非常完美痴鳄。再深入的思考或?qū)嵺`一下瘟斜,就會發(fā)現(xiàn)這個方式運用起來多么麻煩缸夹,哪怕更多的優(yōu)化也不可避免痪寻。有興趣的可以下載這個。
pod 'MultiDelegateOC', '0.0.1'
代理協(xié)議中的每個方法都要主動遍歷調(diào)用每個代理對象虽惭。我們自己新建的類還好橡类,如果我們需要將第三方庫的類變?yōu)槎啻恚胂肽敲炊嗟拇矸椒ㄐ枰膭友看健L热舻谌綆斓念愋略隽瞬糠执矸椒ü嘶覀円惨鄳奶砑印?/p>
如果不想修改第三方庫的代碼,怎么辦匆笤,難道要在外面再封裝一層嗎研侣?想想以后的維護工作就讓人頭疼。
進階的多代理
于是尋找進階的多代理方式已不得不做炮捧,幸好萬能的github有很多大牛庶诡,我們只需要站在他們的肩膀上就好了。
最初的多代理模式之所以有上述的問題咆课,是因為我們讓ComandA
直接管理delegates
數(shù)組末誓,這樣必然會對原有代碼進行改動。
如果我們新建一個類MultiDelegateOC
代替ComandA
管理delegates
數(shù)組书蚪,只需要將ComandA
的@property (weak, nonatomic) id <ReportDelegate> delegate
設置為MultiDelegateOC
對象不就好了喇澡。
這樣原來的ComandA
不需要任何改動就能繼續(xù)使用多代理了。我們只需要在MultiDelegateOC
內(nèi)部實現(xiàn)遍歷調(diào)用就好了殊校。
如果我們理解OC的方法執(zhí)行——消息轉(zhuǎn)發(fā)機制就很容易實現(xiàn)了晴玖。我們只需要截獲MultiDelegateOC
的方法執(zhí)行,將其變?yōu)楸闅v執(zhí)行就可以了箩艺。
這里需要重寫NSObject
的兩個方法methodSignatureForSelector:
和forwardInvocation:
窜醉。
methodSignatureForSelector:
原型:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
這個函數(shù)讓重載方有機會拋出一個函數(shù)的簽名,再由后面的forwardInvocation:去執(zhí)行艺谆。如果當前類沒有實現(xiàn)這個函數(shù)導致返回值為nil榨惰,程序就會crash--未實現(xiàn)的函數(shù)。
forwardInvocation:
原型:
- (void)forwardInvocation:(NSInvocation *)anInvocation
函數(shù)的真正執(zhí)行者静汤,在這個方法中琅催,我們可以從NSInvocation對象中截獲selector,參數(shù)虫给,可以設置selector的調(diào)用者藤抡,真正的遍歷delegates
數(shù)組去執(zhí)行就完全沒有問題了。
//重寫respondsToSelector方法抹估,讓`ComandA`類真實判斷缠黍。
- (BOOL)respondsToSelector:(SEL)selector
{
if ([super respondsToSelector:selector])
{
return YES;
}
for (id delegate in self.delegates)
{
if (delegate && [delegate respondsToSelector:selector])
{
return YES;
}
}
return NO;
}
//防止崩潰,生成函數(shù)簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (signature)
{
return signature;
}
[self.delegates compact];
if (self.silentWhenEmpty && self.delegates.count == 0)
{
// return any method signature, it doesn't really matter
return [self methodSignatureForSelector:@selector(description)];
}
for (id delegate in self.delegates)
{
if (!delegate)
{
continue;
}
signature = [delegate methodSignatureForSelector:selector];
if (signature)
{
break;
}
}
return signature;
}
//遍歷`delegates`數(shù)組調(diào)用代理方法
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = [invocation selector];
BOOL responded = NO;
NSArray *copiedDelegates = [self.delegates copy];
for (id delegate in copiedDelegates)
{
if (delegate && [delegate respondsToSelector:selector])
{
[invocation invokeWithTarget:delegate];
responded = YES;
}
}
if (!responded && !self.silentWhenEmpty)
{
[self doesNotRecognizeSelector:selector];
}
}
一個進階版的多代理模式就完成药蜻,現(xiàn)在我們只需要主動生成一個MultiDelegateOC
對象管理多代理就可以了瓷式。
有興趣的可以下載這個替饿。
pod 'MultiDelegateOC', '0.0.2'
不過現(xiàn)在還不是很完美,如果代理協(xié)議中有返回值的情況贸典,我們并沒有處理视卢。再給- (void)forwardInvocation:
方法添點料就好了:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = [invocation selector];
BOOL responded = NO;
NSArray *copiedDelegates = [self.delegates copy];
void *returnValue = NULL;
for (id delegate in copiedDelegates)
{
if (delegate && [delegate respondsToSelector:selector])
{
[invocation invokeWithTarget:delegate];
if(invocation.methodSignature.methodReturnLength != 0)
{
void *value = nil;
[invocation getReturnValue:&value];
if(value)
{
returnValue = value;
}
}
responded = YES;
}
}
if(returnValue)
{
[invocation setReturnValue:&returnValue];
}
if (!responded && !self.silentWhenEmpty)
{
[self doesNotRecognizeSelector:selector];
}
}
如果多個代理對象都有返回值,最終返回將是最后加入的代理的返回值廊驼。當然NSPointerArray可以調(diào)整成員的順序据过,你也可以自己設置判斷條件來選擇返回值。
總結(jié)
以上是我自己在實現(xiàn)多代理模式的歷程妒挎,很多方法都是使用網(wǎng)絡上大神的成熟經(jīng)驗绳锅,在不斷的使用實踐踩坑中,逐步完善出來酝掩。
包括最初的多代理模式榨呆,我也使用了很長時間,后來實在覺得太麻煩庸队。逼不得已從網(wǎng)上找到第二種方式积蜻,覺得挺好用。
后來測試發(fā)現(xiàn)總會出現(xiàn)莫名其妙的異常彻消,一時之間找不到原因竿拆,不得已又切換到了第一種方式。
后來閑的時候靈光一閃宾尚,發(fā)現(xiàn)第二種方式有異常的情況都是在代理方法有返回值的情況下出現(xiàn)丙笋。知道問題原因,解決起來就簡單多了煌贴。
現(xiàn)在我一直使用第二種代理模式御板,至今沒有出現(xiàn)過問題。
多代理模式我一般是與單例配合使用牛郑。使用多代理的地方還不少怠肋,比如高德地圖SDK。
Demo地址
Pod引用
pod 'MultiDelegateOC'