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

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

為什么要使用多代理模式

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

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

  1. 代理 1對1,高耦合

  2. 通知 1對多些举,松耦合

  3. block

  4. KVO

...

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

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

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

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

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

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

  5. 通知發(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地址

MultiDelegateOC Demo

Pod引用


pod 'MultiDelegateOC'

高德地圖Demo

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淹朋,一起剝皮案震驚了整個濱河市笙各,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌础芍,老刑警劉巖杈抢,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仑性,居然都是意外死亡惶楼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歼捐,“玉大人嫩实,你說我怎么就攤上這事】遥” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵宰缤,是天一觀的道長颂翼。 經(jīng)常有香客問我,道長朦乏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任筹陵,我火速辦了婚禮并思,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘输涕。我一直安慰自己莱坎,他們只是感情好型奥,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布烫葬。 她就那樣靜靜地躺著搭综,像睡著了一般条获。 火紅的嫁衣襯著肌膚如雪帅掘。 梳的紋絲不亂的頭發(fā)上修档,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天院峡,我揣著相機與錄音撕予,去河邊找鬼。 笑死踩寇,一個胖子當著我的面吹牛辣卒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播啡莉,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哮兰!你這毒婦竟也來了奠蹬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤宵距,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了石洗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讲衫。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡枷畏,死狀恐怖氮发,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颈畸,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布华弓,位于F島的核電站困乒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏娜搂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一考廉、第九天 我趴在偏房一處隱蔽的房頂上張望携御。 院中可真熱鬧既绕,春花似錦涮坐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至救巷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浦译,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工淆衷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留渤弛,地道東北人甚带。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像晴氨,于是被迫代替她去往敵國和親碉输。 傳聞我的和親對象是個殘疾皇子籽前,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

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