iOS RunTime 學習記錄4_方法Method

前言:我是參考 南峰子 的博客加上自己理解寫的芳肌,原著專輯大家自己可看:http://southpeak.github.io/categories/objectivec/

一,簡介:Method

1. 在OC 中方法我們用Method來表示味咳,它的定義是一個結(jié)構(gòu)體庇勃,如下:

typedef struct objc_method *Method;

struct objc_method {
SEL method_name     OBJC2_UNAVAILABLE; // 方法名
char *method_types  OBJC2_UNAVAILABLE;
IMP method_imp          OBJC2_UNAVAILABLE; // 方法實現(xiàn)
}

可以看出其主要是:SELIMP槽驶、char *method_types 三部分組成责嚷,我們可以分別來看看:

2. SEL

SEL又叫選擇器,是表示一個方法的selector的指針掂铐,其定義如下:

typedef struct objc_selector *SEL;

方法的selector
用于表示運行時方法的名字罕拂。Objective-C在編譯時,會依據(jù)每一個方法的名字全陨、參數(shù)序列爆班,生成一個唯一的整型標識(Int
類型的地址),這個標識就是SEL
辱姨。如下代碼所示:

SEL sel1 = @selector(method1);
NSLog(@"sel : %p", sel1);

上面的輸出為:

2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72

兩個類之間柿菩,不管它們是父類與子類的關(guān)系,還是之間沒有這種關(guān)系雨涛,只要方法名相同枢舶,那么方法的SEL就是一樣的懦胞。每一個方法都對應(yīng)著一個SEL。所以在Objective-C同一個類(及類的繼承體系)中凉泄,不能存在2個同名的方法躏尉,即使參數(shù)類型不同也不行。相同的方法只能對應(yīng)一個SEL后众。這也就導(dǎo)致Objective-C在處理相同方法名且參數(shù)個數(shù)相同但類型不同的方法方面的能力很差胀糜。

當然,不同的類可以擁有相同的selector蒂誉,這個沒有問題教藻。不同類的實例對象執(zhí)行相同的selector時,會在各自的方法列表中去根據(jù)selector去尋找自己對應(yīng)的IMP

工程中的所有的SEL組成一個Set集合拗盒,Set的特點就是唯一怖竭,因此SEL是唯一的。因此陡蝇,如果我們想到這個方法集合中查找某個方法時,只需要去找到這個方法對應(yīng)的SEL就行了哮肚,SEL實際上就是根據(jù)方法名hash化了的一個字符串登夫,而對于字符串的比較僅僅需要比較他們的地址就可以了,可以說速度上無語倫比T侍恕恼策!但是,有一個問題潮剪,就是數(shù)量增多會增大hash沖突而導(dǎo)致的性能下降(或是沒有沖突涣楷,因為也可能用的是perfect hash)。但是不管使用什么樣的方法加速抗碰,如果能夠?qū)⒖偭繙p少(多個方法可能對應(yīng)同一個SEL)狮斗,那將是最犀利的方法。那么弧蝇,我們就不難理解碳褒,為什么SEL僅僅是函數(shù)名了。

其實說了這么多我總結(jié)的就一個SEL就是根據(jù)方法名稱得到字符串看疗,其已經(jīng)忽略了所帶的參數(shù)是什么沙峻,就是在OC中代表某個方法,它最后要和IMP(具體執(zhí)行的C函數(shù)两芳,標準的C調(diào)用)進行映射綁定摔寨,下面我們就來介紹IMP

3. IMP

實際上是一個函數(shù)指針,指向方法實現(xiàn)的首地址怖辆。其定義如下:

id (*IMP)(id, SEL, ...)

這個函數(shù)使用當前CPU架構(gòu)實現(xiàn)的標準的C調(diào)用約定是复。第一個參數(shù)是指向self的指針(如果是實例方法沉填,則是類實例的內(nèi)存地址;如果是類方法佑笋,則是指向元類的指針)翼闹,第二個參數(shù)是方法選擇器(selector),接下來是方法的實際參數(shù)列表蒋纬。

前面介紹過的SEL就是為了查找方法的最終實現(xiàn)IMP的猎荠。由于每個方法對應(yīng)唯一的SEL,因此我們可以通過SEL
方便快速準確地獲得它所對應(yīng)的IMP蜀备,查找過程將在下面討論关摇。取得IMP后,我們就獲得了執(zhí)行這個方法代碼的入口點碾阁,此時输虱,我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針了。

總結(jié)的說IMP其實就是C函數(shù)指針脂凶,這個函數(shù)必須要傳入兩個參數(shù)第一個是執(zhí)行該函數(shù)的對象宪睹,第二個就是關(guān)聯(lián)的SEL,剩下跟著的參數(shù)就是方法出入的參數(shù)了蚕钦,你后面跟多少亭病,看你自己的了!

二嘶居,方法的調(diào)用流程

1罪帖,基本調(diào)用

在Objective-C中,消息直到運行時才綁定到方法實現(xiàn)上邮屁。編譯器會將消息表達式[receiver message]轉(zhuǎn)化為一個消息函數(shù)的調(diào)用整袁,即objc_msgSend。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù)佑吝,如以下所示

objc_msgSend(receiver, selector)

如果有參數(shù):

objc_msgSend(receiver, selector, arg1, arg2, ...)

1坐昙,首先它找到selector對應(yīng)的方法實現(xiàn)。因為同一個方法可能在不同的類中有不同的實現(xiàn)迹蛤,所以我們需要依賴于接收者的類來找到的確切的實現(xiàn)民珍。
2,它調(diào)用方法實現(xiàn)盗飒,并將接收者對象及方法的所有參數(shù)傳給它嚷量。
3,最后逆趣,它將實現(xiàn)返回的值作為它自己的返回值蝶溶。

通俗而言,我們在用一個實例或者是類調(diào)用一個方法,這個實例或者是類就是Receiver抖所,方法的字符串就是對應(yīng)selector梨州,這個方法對應(yīng)的參數(shù)就往后面依次排開agr1,arg2.....

2, 消息轉(zhuǎn)發(fā)(message forwarding)

這才是真正核心的地方,其實上面講的基本調(diào)用也就是正常轉(zhuǎn)發(fā)田轧,但如果一個對象無法接收指定消息時暴匠,又會發(fā)生什么事呢?默認情況下傻粘,如果是以[object message]的方式調(diào)用方法每窖,如果object無法響應(yīng)message消息時,編譯器會報錯弦悉。但如果是以perform...的形式來調(diào)用窒典,則需要等到運行時才能確定object是否能接收message消息。如果不能稽莉,則程序崩潰瀑志。通常,當我們不能確定一個對象是否能接收某個消息時污秆,會先調(diào)用respondsToSelector:
來判斷一下劈猪。當一個對象無法接收某一消息時,就會啟動所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機制混狠,通過這一機制岸霹,我們可以告訴對象如何處理未知的消息。默認情況下将饺,對象接收到未知的消息,會導(dǎo)致程序崩潰,如下代碼所示:

 unrecognized selector sent to instance 0x100111940
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940'

這段異常信息實際上是由NSObject的”doesNotRecognizeSelector“方法拋出的痛黎。不過予弧,我們可以采取一些措施,讓我們的程序執(zhí)行特定的邏輯湖饱,而避免程序的崩潰掖蛤。

消息轉(zhuǎn)發(fā)機制基本上分為三個步驟:
1>,動態(tài)方法解析------->2>,備用接收者-------->3>,完整消息轉(zhuǎn)發(fā)

1>動態(tài)方法解析

對象在接收到未知的消息時,首先會調(diào)用所屬類的類方法+resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)井厌。在這個方法中蚓庭,我們有機會為該未知消息新增一個”處理方法””。不過使用該方法的前提是我們已經(jīng)實現(xiàn)了該”處理方法”仅仆,只需要在運行時通過class_addMethod函數(shù)動態(tài)添加到類里面就可以了器赞。
例如我又一個Person類,我在外面通過performSelector 調(diào)用了它的一個實例方法testMethod1墓拜,并傳入了兩個參數(shù)

    /*******方法傳遞***********/
    Person *testPerson = [[Person alloc]init];
    [testPerson performSelector:@selector(testMethod1) withObject:@"小胡" withObject:@"酒鬼"];

在我的Person類中我沒有testMethod1這個方法港柜,我需要怎么處理了?

//C函數(shù)中IMP其實就是標準的 C函數(shù)調(diào)用
void IMPTestMethod1Funcation(id self,SEL _cmd,NSString *name,NSString *nick){
    NSLog(@"IMP C 函數(shù)中實現(xiàn):%@, %p", self, _cmd);
    NSLog(@"我?guī)氲膮?shù)是name:%@ nick:%@ ",name,nick);
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSString *selString  = NSStringFromSelector(sel);
    NSLog(@"調(diào)用的方法是什么:%@",selString);
    if ([selString isEqualToString:@"testMethod1"]) {
        class_addMethod(self.class, sel, (IMP)IMPTestMethod1Funcation, "@:@@");
    }
    return [super resolveInstanceMethod:sel];
}

首先,當調(diào)用方法testMethod1在Person找不對應(yīng)的方法夏醉,會進入+(BOOL)resolveInstanceMethod:(SEL)sel這個方法中爽锥,在這里你可以通過判斷方法的名字(字符串)來判斷要處理的方法。然后通過class_addMethod來為這個方法動態(tài)綁定一個實現(xiàn)方法畔柔。這個實現(xiàn)方法是一個標準的C調(diào)用方法氯夷,如例子中的:void IMPTestMethod1Funcation(id self,SEL _cmd,NSString *name,NSString *nick)其中前兩個參數(shù)是必須的:
第一個id self 表示方法執(zhí)行的對象,
第二個SEL _cmd 表示這個C方法要替代那個OC方法方法的SEL靶擦。
后面就是被替代OC方法方法要根的參數(shù)(我這個OC方法有兩個參數(shù)腮考,所以后面跟上兩個參數(shù))

打印結(jié)果:

2016-10-31 15:30:06.205 ObRunTime[35215:6061873] 調(diào)用的方法是什么:testMethod1
2016-10-31 15:30:06.205 ObRunTime[35215:6061873] 調(diào)用的方法是什么:_dynamicContextEvaluation:patternString:
2016-10-31 15:30:06.206 ObRunTime[35215:6061873] 調(diào)用的方法是什么:descriptionWithLocale:
2016-10-31 15:30:06.206 ObRunTime[35215:6061873] IMP C 函數(shù)中實現(xiàn):<Person: 0x600000024440>, testMethod1
2016-10-31 15:30:06.206 ObRunTime[35215:6061873] 我?guī)氲膮?shù)是name:小胡 nick:酒鬼 

關(guān)于

class_addMethod(Class cls, SEL name, IMP imp, const char *types) 

第四個參數(shù)*types,解釋一下奢啥,比較讓人暈秸仙,這里摸索后解釋一下:
我這里用的是@:@@,其實完整寫法是v@:@@桩盲,其中v@:表示返回值是void(沒有返回值)寂纪,后面的兩個@分別表示這個方法傳入了兩個參數(shù)。如果你只傳了一個參數(shù)就用v@:@就可以赌结。還有i@:表示能有一個int返回值捞蛋,后面參數(shù)跟幾個同理了。

當然如果你覺的class_addMethod中添加的是IMP是一個C函數(shù)看的不習慣的話柬姚,可以這樣寫:

class_addMethod(self.class, sel, class_getMethodImplementation(self, @selector(你的oc方法)), "v@:@@");
//替換C函數(shù)的OC函數(shù)
-(void)test:(NSString *)agr1 agr2:(NSString *)agr2{
    NSLog(@"我?guī)氲膮?shù)是name:%@ nick:%@ ",agr1,agr2);
    NSLog(@"要做什么事TODO");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSString *selString  = NSStringFromSelector(sel);
    NSLog(@"調(diào)用的方法是什么:%@",selString);
    if ([selString isEqualToString:@"testMethod1"]) {
//        class_addMethod(self.class, sel, (IMP)IMPTestMethod1Funcation, "@:@@");
        class_addMethod(self.class, sel, class_getMethodImplementation(self, @selector(test:agr2:)), "v@:@@");
    }
    return [super resolveInstanceMethod:sel];
}

打印結(jié)果:

2016-10-31 16:03:46.543 ObRunTime[35350:6168967] 調(diào)用的方法是什么:testMethod1
2016-10-31 16:03:46.543 ObRunTime[35350:6168967] 我?guī)氲膮?shù)是name:小胡 nick:酒鬼 
2016-10-31 16:03:46.544 ObRunTime[35350:6168967] 要做什么事TODO
2>,備用接收者

如果在上一步無法處理消息拟杉,則Runtime會繼續(xù)調(diào)以下方法:

-(id)forwardingTargetForSelector:(SEL)aSelector

如果一個對象實現(xiàn)了這個方法,并返回一個非nil的結(jié)果量承,則這個對象會作為消息的新接收者搬设,且消息會被分發(fā)到這個對象。當然這個對象不能是self自身撕捍,否則就是出現(xiàn)無限循環(huán)拿穴。當然,如果我們沒有指定相應(yīng)的對象來處理aSelector忧风,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果默色。
使用這個方法通常是在對象內(nèi)部,可能還有一系列其它對象能處理該消息狮腿,我們便可借這些對象來處理消息并返回腿宰,這樣在對象外部看來,還是由該對象親自處理了這一消息缘厢。
如下我申明了一個ObjectHelper

#import "ObjectHelper.h"

@implementation ObjectHelper

-(void)testMethodHelp:(NSString *)string{
    NSLog(@"方法轉(zhuǎn)移到這個類來實現(xiàn)了 參數(shù):%@",string);
}

@end

現(xiàn)在我?直接調(diào)用Person的這個方法吃度,因為Person沒有這個方法,我把它轉(zhuǎn)發(fā)給ObjectHelper

Person *testPerson = [[Person alloc]init];
[testPerson performSelector:@selector(testMethodHelp:) withObject:@"轉(zhuǎn)發(fā)測試"];
/***********消息轉(zhuǎn)發(fā)給別的類處理************/
//如果resolveInstanceMethod 無法處理消息 就會給你一次機會 讓你把這個方法分發(fā)的別的對象去處理
-(id)forwardingTargetForSelector:(SEL)aSelector{
    NSString *selString = NSStringFromSelector(aSelector);
    if ([selString isEqualToString:@"testMethodHelp:"]) {
        return [[ObjectHelper alloc]init];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

控制臺打用列濉:

2016-10-31 17:43:20.447 ObRunTime[35692:6278785] 調(diào)用的方法是什么:testMethodHelp:
2016-10-31 17:43:20.447 ObRunTime[35692:6278785] 不是當前要研究的方法
2016-10-31 17:43:20.447 ObRunTime[35692:6278785] 方法轉(zhuǎn)移到這個類來實現(xiàn)了 參數(shù):轉(zhuǎn)發(fā)測試
3>,完整消息轉(zhuǎn)發(fā)

如果在上一步還不能處理未知消息规肴,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了。此時會調(diào)用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

運行時系統(tǒng)會在這一步給消息接收者最后一次機會將消息轉(zhuǎn)發(fā)給其它對象(不止一個對象,可以是多個)拖刃。對象會創(chuàng)建一個表示消息的NSInvocation對象删壮,把與尚未處理的消息有關(guān)的全部細節(jié)都封裝在anInvocation中,包括selector兑牡,目標(target)和參數(shù)央碟。我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象。
forwardInvocation:方法的實現(xiàn)有兩個任務(wù):

  1. 定位可以響應(yīng)封裝在anInvocation中的消息的對象均函。這個對象不需要能處理所有未知消息亿虽。
  2. 使用anInvocation作為參數(shù),將消息發(fā)送到選中的對象苞也。anInvocation將會保留調(diào)用結(jié)果洛勉,運行時系統(tǒng)會提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者。

不過如迟,在這個方法中我們可以實現(xiàn)一些更復(fù)雜的功能收毫,我們可以對消息的內(nèi)容進行修改,比如追回一個參數(shù)等殷勘,然后再去觸發(fā)消息此再。另外,若發(fā)現(xiàn)某個消息不應(yīng)由本類處理玲销,則應(yīng)調(diào)用父類的同名方法输拇,以便繼承體系中的每個類都有機會處理此調(diào)用請求。
還有一個很重要的問題贤斜,我們必須重寫以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象策吠。因此我們必須重寫這個方法,為給定的selector提供一個合適的方法簽名瘩绒。
其實簽名奴曙,可以理解找到一個實現(xiàn)該方法的類,然后把這個方法的名稱草讶、參數(shù)、返回值包裝起來炉菲,打一個包堕战!
下面舉一個例子,還是我的Person類拍霜,我在ViewController調(diào)用了一個他沒有實現(xiàn)的方法testMethodInvork

/*******消息完全轉(zhuǎn)發(fā)***********/
 Person *testPerson = [[Person alloc]init];
 [testPerson performSelector:@selector(testMethodInvork)];

我們把消息分發(fā)給ObjectHelper處理嘱丢,ObjectHelper實現(xiàn)testMethodInvork方法

-(void)testMethodInvork{
    NSLog(@"天啊,消息轉(zhuǎn)發(fā)了");
}

Person中的實現(xiàn):

/**********處理未知消息的步驟**********/

//C函數(shù)中IMP其實就是標準的 C函數(shù)調(diào)用
void IMPTestMethod1Funcation(id self,SEL _cmd,NSString *name,NSString *nick){
    NSLog(@"IMP C 函數(shù)中實現(xiàn):%@, %@", self,  NSStringFromSelector(_cmd));
    NSLog(@"我?guī)氲膮?shù)是name:%@ nick:%@ ",name,nick);
}

//替換C函數(shù)的OC函數(shù)
-(void)test:(NSString *)agr1 agr2:(NSString *)agr2{
    NSLog(@"我?guī)氲膮?shù)是name:%@ nick:%@ ",agr1,agr2);
    NSLog(@"要做什么事TODO");
}

/***********1祠饺,動態(tài)方法解析(運行的時候添加實現(xiàn)方法)************/
+(BOOL)resolveInstanceMethod:(SEL)sel{
    
    NSLog(@"第一步:處理未知消息");
    
    NSString *selString  = NSStringFromSelector(sel);
    NSLog(@"調(diào)用的方法是什么:%@",selString);
    if ([selString isEqualToString:@"testMethod1"]) {
//        class_addMethod(self.class, sel, (IMP)IMPTestMethod1Funcation, "@:@@");
        class_addMethod(self.class, sel, class_getMethodImplementation(self, @selector(test:agr2:)), "v@:@@");
    }else{
        NSLog(@"第一步中不是當前要研究的方法");
    }
    return [super resolveInstanceMethod:sel];
}

/***********2越驻,消息轉(zhuǎn)發(fā)給別的類處理(如果1中途不對方法進行處理)************/
//如果resolveInstanceMethod 無法處理消息 就會給你一次機會 讓你把這個方法分發(fā)的別的對象去處理
-(id)forwardingTargetForSelector:(SEL)aSelector{
    
    NSLog(@"第二步:轉(zhuǎn)發(fā)消息給別的類實現(xiàn)");
    
    NSString *selString = NSStringFromSelector(aSelector);
    if ([selString isEqualToString:@"testMethodHelp:"]) {
        return [[ObjectHelper alloc]init];
    }else{
        NSLog(@"第二步?jīng)]有轉(zhuǎn)發(fā)消息");
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

/***********3,完整的消息轉(zhuǎn)發(fā) (1 和 2 都不處理這個消息,就把這個消息封裝出來缀旁,實現(xiàn)完整轉(zhuǎn)發(fā))************/
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    
    NSLog(@"第三步:最后的完全轉(zhuǎn)發(fā)");
    
    if([ObjectHelper instancesRespondToSelector:anInvocation.selector]){
        
        ObjectHelper *helper = [[ObjectHelper alloc]init];
        [anInvocation invokeWithTarget:helper];
    }
}

//簽名這個方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if([ObjectHelper instancesRespondToSelector:aSelector]){
            signature = [ObjectHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
}

運行結(jié)果:

2016-11-02 11:26:47.638 ObRunTime[41985:8436765] 第一步:處理未知消息
2016-11-02 11:26:47.638 ObRunTime[41985:8436765] 調(diào)用的方法是什么:testMethodInvork
2016-11-02 11:26:47.638 ObRunTime[41985:8436765] 第一步中不是當前要研究的方法
2016-11-02 11:26:47.639 ObRunTime[41985:8436765] 第二步:轉(zhuǎn)發(fā)消息給別的類實現(xiàn)
2016-11-02 11:26:47.639 ObRunTime[41985:8436765] 第二步?jīng)]有轉(zhuǎn)發(fā)消息
2016-11-02 11:26:47.639 ObRunTime[41985:8436765] 第一步:處理未知消息
2016-11-02 11:26:47.639 ObRunTime[41985:8436765] 調(diào)用的方法是什么:testMethodInvork
2016-11-02 11:26:47.640 ObRunTime[41985:8436765] 第一步中不是當前要研究的方法
2016-11-02 11:26:47.640 ObRunTime[41985:8436765] 第三步:最后的完全轉(zhuǎn)發(fā)
2016-11-02 11:26:47.640 ObRunTime[41985:8436765] 天啊记劈,消息轉(zhuǎn)發(fā)了

上面這個例子能很清楚的看到,消息轉(zhuǎn)發(fā)的三個步驟并巍,是不是很爽目木,哈哈!

下面舉一個實際的開發(fā)場景懊渡,大家可以看到這篇博客:http://kittenyang.com/forwardinvocation/
問題描述刽射,看博客就行坎吻,簡單的一句話就是八秃,UIScorllView的委托Delegate,能不能讓兩個或更多的實例同時相應(yīng)风范!
我們就可以用到消息轉(zhuǎn)發(fā)機制肾档,讓多個對象同時響應(yīng)Delegate 摹恰,為此我們創(chuàng)建一個Delegate分發(fā)類DelegateRouter.h,讓它響應(yīng)目標delegate阁最,因為它沒有實現(xiàn)delegate戒祠,所以通過消息轉(zhuǎn)發(fā) 分發(fā)給目標1:ViewController和 目標2:SecondDelegate
DelegateRouter.h文件如下:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#import "ViewController.h"
#import "SecondDelegate.h"

@interface DelegateRouter : NSObject

@property (weak,nonatomic) ViewController *vcDelegate;
@property (strong,nonatomic) SecondDelegate *secDelegate;

@end

DelegateRouter.m文件如下:

#import "DelegateRouter.h"

@implementation DelegateRouter

-(BOOL)respondsToSelector:(SEL)aSelector{
//    NSString *test = NSStringFromSelector(aSelector);
//    NSLog(@"響應(yīng)方法:%@",test);
    
    if ([self.vcDelegate respondsToSelector:aSelector] ||[self.secDelegate respondsToSelector:aSelector]) {
        return YES;
    }else{
        return NO;
    }
}
//+(BOOL)resolveInstanceMethod:(SEL)sel{
//    return [super resolveInstanceMethod:sel];
//}

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    if ([self.vcDelegate respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self.vcDelegate];
    }
    
    if ([self.secDelegate respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self.secDelegate];
    }
}

//方法簽名就是 配置一段方法字符串和參數(shù)的唯一性,不是和類進行綁定
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        NSMethodSignature *firstMethodSignature = [self.vcDelegate methodSignatureForSelector:aSelector];
        NSMethodSignature *secondMethodSignature = [self.secDelegate methodSignatureForSelector:aSelector];
        
        if (firstMethodSignature) {
            NSLog(@"注冊1");
            methodSignature = firstMethodSignature;
        }else if (secondMethodSignature){
            NSLog(@"注冊2");
            methodSignature = secondMethodSignature;
        }
        return methodSignature;
    }
    return methodSignature;
}

@end

這個需要強調(diào):

  1. 我需要路由類DelegateRouter.h響應(yīng)我需要委托的方法速种,所以加上判斷-(BOOL)respondsToSelector:(SEL)aSelector姜盈,只有這個返回YES 這個對象才響應(yīng)這個方法,才會調(diào)用【object sendmsg】配阵,如果沒有實現(xiàn)方法馏颂,才會走消息的轉(zhuǎn)發(fā)流程。這里我只需要處理ViewControllerSecondDelegate里的指定委托棋傍,所以我加了判斷救拉,別的我直接不讓它響應(yīng),他就無法轉(zhuǎn)發(fā)瘫拣。
  2. -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector簽名方法亿絮,不針對類,只要你這個類實現(xiàn)了你要執(zhí)行的方法麸拄,都可以簽名這個方法派昧,這里實現(xiàn)加個if~else其實只會執(zhí)行注冊1

我們的兩個響應(yīng)委托的地方,一個是ViewController

#pragma mark - UIScrollViewDelegate
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    NSLog(@"我在第一出響應(yīng)delegate");
}

一個是SecondDelegate類中

@implementation SecondDelegate

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    NSLog(@"我在第二次響應(yīng)Delegate");
}

我們再給ScrollViewDelegate 賦值的時候就選擇賦給'DelegateRouter'

delegateRouter = [[DelegateRouter alloc]init];
    delegateRouter.vcDelegate = self;
    delegateRouter.secDelegate = [[SecondDelegate alloc]init];
    
    UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:self.view.bounds];
    scrollView.delegate = delegateRouter;
    scrollView.backgroundColor = [UIColor orangeColor];
    scrollView.contentSize = CGSizeMake(CGRectGetWidth(self.view.frame), 700);
    [self.view addSubview:scrollView];

這里需要注意拢切,你響應(yīng)的Delegate必須是各全局或者靜態(tài)變量蒂萎,不然無法執(zhí)行Delegate。

執(zhí)行控制臺輸出:

2016-11-02 16:08:55.445 ObRunTime[42837:8844820] 注冊1
2016-11-02 16:08:55.445 ObRunTime[42837:8844820] 我在第一出響應(yīng)delegate
2016-11-02 16:08:55.446 ObRunTime[42837:8844820] 我在第二次響應(yīng)Delegate
2016-11-02 16:08:55.465 ObRunTime[42837:8844820] 注冊1
2016-11-02 16:08:55.465 ObRunTime[42837:8844820] 我在第一出響應(yīng)delegate
2016-11-02 16:08:55.465 ObRunTime[42837:8844820] 我在第二次響應(yīng)Delegate
2016-11-02 16:08:55.496 ObRunTime[42837:8844820] 注冊1
2016-11-02 16:08:55.496 ObRunTime[42837:8844820] 我在第一出響應(yīng)delegate
2016-11-02 16:08:55.497 ObRunTime[42837:8844820] 我在第二次響應(yīng)Delegate
2016-11-02 16:08:55.514 ObRunTime[42837:8844820] 注冊1

兩個類同時響應(yīng)多個淮椰,同理五慈,你也可以弄更多的對象纳寂!

以上代碼完整版下載:鏈接: http://pan.baidu.com/s/1kVdOizH 密碼: ijtv

博客參照:http://southpeak.github.io/2014/11/03/objective-c-runtime-3/
自己理解,歡迎板磚P豪埂1形摺!聪轿!哈哈R巍!陆错!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灯抛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子音瓷,更是在濱河造成了極大的恐慌对嚼,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绳慎,死亡現(xiàn)場離奇詭異纵竖,居然都是意外死亡,警方通過查閱死者的電腦和手機杏愤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門靡砌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人珊楼,你說我怎么就攤上這事通殃。” “怎么了厕宗?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵画舌,是天一觀的道長。 經(jīng)常有香客問我已慢,道長曲聂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任佑惠,我火速辦了婚禮朋腋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膜楷。我一直安慰自己乍丈,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布把将。 她就那樣靜靜地躺著,像睡著了一般忆矛。 火紅的嫁衣襯著肌膚如雪察蹲。 梳的紋絲不亂的頭發(fā)上请垛,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音洽议,去河邊找鬼宗收。 笑死,一個胖子當著我的面吹牛亚兄,可吹牛的內(nèi)容都是我干的混稽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼审胚,長吁一口氣:“原來是場噩夢啊……” “哼匈勋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起膳叨,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洽洁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后菲嘴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饿自,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年龄坪,在試婚紗的時候發(fā)現(xiàn)自己被綠了昭雌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡健田,死狀恐怖烛卧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抄课,我是刑警寧澤唱星,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站跟磨,受9級特大地震影響间聊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抵拘,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一哎榴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧僵蛛,春花似錦尚蝌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至驼侠,卻和暖如春姿鸿,著一層夾襖步出監(jiān)牢的瞬間谆吴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工苛预, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留句狼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓热某,卻偏偏與公主長得像腻菇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子昔馋,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉筹吐,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢绒极?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,193評論 0 7
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,556評論 33 466
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 758評論 0 1
  • 繼上Runtime梳理(四) 通過前面的學習骏令,我們了解到Objective-C的動態(tài)特性:Objective-C不...
    小名一峰閱讀 752評論 0 3