iOS如何實現(xiàn)多代理模式--OC

OC 如何實現(xiàn)多代理模式

為什么要使用多代理模式

標(biāo)題雖然是如何實現(xiàn)多代理模式,但是知道為什么需要實現(xiàn)多代理模式同樣重要。

眾所周知,OC的常用的消息傳遞方式有很多種,各有各的好處,在不同的場景選擇不同實現(xiàn)方式。如:

  1. 代理 1對1,高耦合

  2. 通知 1對多,松耦合

  3. block

  4. KVO

...

不同的實現(xiàn)方式有不同的應(yīng)用場景,也有各自的優(yōu)缺點。普通的代理模式只能應(yīng)用與1對1的場景,針對1對多的場景只能被迫選擇使用通知。

但是通知也有自己的缺點:

  1. 在編譯期間不會檢查通知是否能夠被觀察者正確的處理;

  2. 在釋放通知的觀察者時,需要在通知中心移除觀察者;

  3. 在調(diào)試的時候,通知傳遞的過程很難控制和跟蹤;

  4. 發(fā)送通知和接收通知時需要提前知道通知名稱,如果通知名稱不一致,會出現(xiàn)不同步的情況;

  5. 通知發(fā)出后,不能從觀察者獲得任何的反饋信息。對于需要返回值的場景沒有辦法處理。

如果代理模式能支持多個響應(yīng)對象,那么就不會再有以上的問題。

如何實現(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ù),相當(dāng)于弱引用,在釋放一個delegate前,就算不將其從delegates數(shù)組中移除也不會有問題。

一切看起來非常完美,對的,只是看起來非常完美。再深入的思考或?qū)嵺`一下,就會發(fā)現(xiàn)這個方式運用起來多么麻煩,哪怕更多的優(yōu)化也不可避免。有興趣的可以下載這個。


pod 'MultiDelegateOC', '0.0.1'

代理協(xié)議中的每個方法都要主動遍歷調(diào)用每個代理對象。我們自己新建的類還好,如果我們需要將第三方庫的類變?yōu)槎啻恚胂肽敲炊嗟拇矸椒ㄐ枰膭?。倘若第三方庫的類新增了部分代理方法,我們也要相?yīng)的添加。

如果不想修改第三方庫的代碼,怎么辦,難道要在外面再封裝一層嗎?想想以后的維護工作就讓人頭疼。

進階的多代理

于是尋找進階的多代理方式已不得不做,幸好萬能的github有很多大牛,我們只需要站在他們的肩膀上就好了。

最初的多代理模式之所以有上述的問題,是因為我們讓ComandA直接管理delegates數(shù)組,這樣必然會對原有代碼進行改動。

如果我們新建一個類MultiDelegateOC代替ComandA管理delegates數(shù)組,只需要將ComandA@property (weak, nonatomic) id <ReportDelegate> delegate設(shè)置為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í)行。如果當(dāng)前類沒有實現(xiàn)這個函數(shù)導(dǎo)致返回值為nil,程序就會crash--未實現(xiàn)的函數(shù)。

forwardInvocation:

原型:


- (void)forwardInvocation:(NSInvocation *)anInvocation

函數(shù)的真正執(zhí)行者,在這個方法中,我們可以從NSInvocation對象中截獲selector,參數(shù),可以設(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];

 }

}

如果多個代理對象都有返回值,最終返回將是最后加入的代理的返回值。當(dāng)然NSPointerArray可以調(diào)整成員的順序,你也可以自己設(shè)置判斷條件來選擇返回值。

總結(jié)

以上是我自己在實現(xiàn)多代理模式的歷程,很多方法都是使用網(wǎng)絡(luò)上大神的成熟經(jīng)驗,在不斷的使用實踐踩坑中,逐步完善出來。

包括最初的多代理模式,我也使用了很長時間,后來實在覺得太麻煩。逼不得已從網(wǎng)上找到第二種方式,覺得挺好用。

后來測試發(fā)現(xiàn)總會出現(xiàn)莫名其妙的異常,一時之間找不到原因,不得已又切換到了第一種方式。

后來閑的時候靈光一閃,發(fā)現(xiàn)第二種方式有異常的情況都是在代理方法有返回值的情況下出現(xiàn)。知道問題原因,解決起來就簡單多了。

現(xiàn)在我一直使用第二種代理模式,至今沒有出現(xiàn)過問題。

多代理模式我一般是與單例配合使用。使用多代理的地方還不少,比如高德地圖SDK。

Demo地址

MultiDelegateOC Demo

Pod引用


pod 'MultiDelegateOC'

高德地圖Demo

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

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,192評論 8 265
  • OC語言基礎(chǔ) 1.類與對象 類方法 OC的類方法只有2種:靜態(tài)方法和實例方法兩種 在OC中,只要方法聲明在@int...
    奇異果好補閱讀 4,507評論 0 11
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,579評論 30 472
  • 今天整理了一下項目中常用的一些控件功能,整理一些UI效果來總結(jié)一下。 如今的APP開發(fā)中,UITableView是...
    Originalee閱讀 8,343評論 2 19
  • 今天一位老鄉(xiāng)打電話和我哭訴,她老媽因為她不愿意做的某件事情而用不好聽的話和不舒服的語調(diào)說她。 我讓她先平復(fù)心情再和...
    Jocelynwang閱讀 333評論 0 1

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