消息傳遞機(jī)制
在OC中,方法的調(diào)用不再理解為對(duì)象調(diào)用其方法,而是要理解成對(duì)象接收消息,消息的發(fā)送采用‘動(dòng)態(tài)綁定’機(jī)制,具體會(huì)調(diào)用哪個(gè)方法直到運(yùn)行時(shí)才能確定趁冈,確定后才會(huì)去執(zhí)行綁定的代碼。方法的調(diào)用實(shí)際就是告訴對(duì)象要干什么拜马,給對(duì)象(的指針)傳送一個(gè)消息渗勘,對(duì)象為接收者(receiver),調(diào)用的方法及其參數(shù)即消息(message)俩莽,給一個(gè)對(duì)象傳消息表達(dá)為:[receiver message]; 接受者的類型可以通過(guò)動(dòng)態(tài)類型識(shí)別于運(yùn)行時(shí)確定旺坠。
在消息傳遞機(jī)制中,當(dāng)開(kāi)發(fā)者編寫[receiver message];語(yǔ)句發(fā)送消息后豹绪,編譯器都會(huì)將其轉(zhuǎn)換成對(duì)應(yīng)的一條objc_msgSend C語(yǔ)言消息發(fā)送原語(yǔ)价淌,具體格式為: void objc_msgSend (id self, SEL cmd, ...)
這個(gè)原語(yǔ)函數(shù)參數(shù)可變,第一個(gè)參數(shù)填入消息的接受者瞒津,第二個(gè)參數(shù)是消息‘選擇子’蝉衣,后面跟著可選的消息的參數(shù)。有了這些參數(shù)巷蚪,objc_msgSend就可以通過(guò)接受者的的isa指針病毡,到其類對(duì)象中的方法列表中以選擇子的名稱為‘鍵’尋找對(duì)應(yīng)的方法,找到則轉(zhuǎn)到其實(shí)現(xiàn)代碼執(zhí)行屁柏,找不到則繼續(xù)根據(jù)繼承關(guān)系從父類中尋找啦膜,如果到了根類還是無(wú)法找到對(duì)應(yīng)的方法有送,說(shuō)明該接受者對(duì)象無(wú)法響應(yīng)該消息,則會(huì)觸發(fā)‘消息轉(zhuǎn)發(fā)機(jī)制’僧家,給開(kāi)發(fā)者最后一次挽救程序崩潰的機(jī)會(huì)雀摘。
消息轉(zhuǎn)發(fā)機(jī)制:
如果消息傳遞過(guò)程中,接受者無(wú)法響應(yīng)收到的消息八拱,則會(huì)觸發(fā)進(jìn)入‘消息轉(zhuǎn)發(fā)’機(jī)制阵赠。
消息轉(zhuǎn)發(fā)依次提供了三道防線,任何一個(gè)起作用都可以挽救此次消息轉(zhuǎn)發(fā)肌稻。按照先后順序三道防線依次為:
動(dòng)態(tài)補(bǔ)加方法的實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel
直接返回消息轉(zhuǎn)發(fā)到的對(duì)象(將消息發(fā)送給另一個(gè)對(duì)象去處理)
- (id)forwardingTargetForSelector:(SEL)aSelector
手動(dòng)生成方法簽名并轉(zhuǎn)發(fā)給另一個(gè)對(duì)象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
示例
這里以一個(gè)簡(jiǎn)單的例子展示消息轉(zhuǎn)發(fā)的完整個(gè)過(guò)程清蚀。定義一個(gè)Test類,類頭文件聲明一個(gè)名為instanceMethod的實(shí)例方法但不提供方法實(shí)現(xiàn)(消息轉(zhuǎn)發(fā)主要就針對(duì)實(shí)例方法爹谭,類方法由于無(wú)法在運(yùn)行時(shí)動(dòng)態(tài)添加實(shí)現(xiàn)等事實(shí)并不能轉(zhuǎn)發(fā)給其他類):
/* Test.h */
@interface Test : NSObject
/* 只聲明一個(gè)實(shí)例方法而不在.m文件中實(shí)現(xiàn) */
- (void)instanceMethod;
@end
然后在main函數(shù)中實(shí)例化Test對(duì)象并調(diào)用該實(shí)例方法枷邪,由于方法沒(méi)有實(shí)現(xiàn),因此在運(yùn)行時(shí)一定會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制:
/* main.m */
#import "Test.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
Test *test = [[Test alloc] init];
[test instanceMethod];
return 0;
}
先進(jìn)入消息轉(zhuǎn)發(fā)的第一道防線诺凡,我們?cè)赥est類的.m文件中提供運(yùn)行時(shí)的轉(zhuǎn)發(fā)接應(yīng)东揣,實(shí)現(xiàn)resolveInstanceMethod方法為指定的instanceMethod消息補(bǔ)加對(duì)應(yīng)方法的實(shí)現(xiàn)完成補(bǔ)救:
/* Test.m */
#import <objc/runtime.h>
/*
* 被動(dòng)態(tài)添加的實(shí)例方法實(shí)現(xiàn)
*/
void instanceMethod(id self, SEL _cmd) {
NSLog(@"收到消息后會(huì)執(zhí)行此處的函數(shù)實(shí)現(xiàn)...");
}
/*
* 動(dòng)態(tài)補(bǔ)加方法實(shí)現(xiàn)
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(instanceMethod)) {
class_addMethod(self, sel, (IMP)instanceMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
如果沒(méi)有實(shí)現(xiàn)resolveInstanceMethod方法就行補(bǔ)救或者直接返回了NO,則進(jìn)入第二道防線绑洛,這里我們要實(shí)現(xiàn)forwardingTargetForSelector函數(shù)返回另一個(gè)實(shí)例對(duì)象救斑,讓該對(duì)象代替原對(duì)象去處理這個(gè)消息童本。 假設(shè)我們讓一個(gè)叫做Test2的類對(duì)象去處理這個(gè)消息真屯,Test2類中要有同名的方法和方法的實(shí)現(xiàn),這樣就會(huì)執(zhí)行Test2中的同名方法完成消息轉(zhuǎn)發(fā):
/* Test2.h */
@interface Test2 : NSObject
- (void)instanceMethod;
@end
/* Test2.m */
@implementation Test2
- (void)instanceMethod {
NSLog(@"消息轉(zhuǎn)發(fā)到這...");
}
@end
/* Test.m */
- (id)forwardingTargetForSelector:(SEL)aSelector {
/* 返回轉(zhuǎn)發(fā)的對(duì)象實(shí)例 */
if (aSelector == @selector(instanceMethod)) {
return [[Test2 alloc] init];
}
return nil;
}
如果沒(méi)有實(shí)現(xiàn)上面的兩個(gè)補(bǔ)救方法或者forwardingTargetForSelector方法直接返回了nil,則進(jìn)入最后一道防線穷娱,此時(shí)我們要手動(dòng)生成方法簽名并實(shí)現(xiàn)forwardInvocation方法將消息轉(zhuǎn)發(fā)給另一個(gè)對(duì)象绑蔫,同第二道防線類似:
/* Test.m */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
/* 為指定的方法手動(dòng)生成簽名 */
NSString *selName = NSStringFromSelector(aSelector);
if ([selName isEqualToString:@"instanceMethod"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
/* 如果另一個(gè)對(duì)象可以相應(yīng)該消息,則將消息轉(zhuǎn)發(fā)給他 */
SEL sel = [anInvocation selector];
Test2 *test2 = [[Test2 alloc] init];
if ([test2 respondsToSelector:sel]) {
[anInvocation invokeWithTarget:test2];
}
}
完整代碼
#import <Foundation/Foundation.h>
#import "Test.h"
int main(int argc, const char * argv[]) {
Test *test = [[Test alloc] init];
[test instanceMethod];
return 0;
}
#import <Foundation/Foundation.h>
@interface Test : NSObject
- (void)instanceMethod;
@end
#import "Test.h" #import "Test2.h" #import <objc/runtime.h>
@implementation Test
/* * 被動(dòng)態(tài)添加的實(shí)例方法實(shí)現(xiàn) */
void instanceMethod(id self, SEL _cmd) {
NSLog(@"收到消息會(huì)執(zhí)行此處的函數(shù)實(shí)現(xiàn)...");
}
/* * 1.第一道防線:動(dòng)態(tài)補(bǔ)加方法實(shí)現(xiàn) */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// return NO; if (sel == @selector(instanceMethod)) {
class_addMethod(self, sel, (IMP)instanceMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
/* * 2.第二道防線 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
// return nil; /* 返回轉(zhuǎn)發(fā)的對(duì)象實(shí)例 */
if (aSelector == @selector(instanceMethod)) {
return [[Test2 alloc] init];
}
return nil;
}
/* * 3.第三道防線 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
/* 為指定的方法手動(dòng)生成簽名 */
NSString *selName = NSStringFromSelector(aSelector);
if ([selName isEqualToString:@"instanceMethod"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
/* 如果另一個(gè)對(duì)象可以相應(yīng)該消息泵额,則將消息轉(zhuǎn)發(fā)給他 */
SEL sel = [anInvocation selector];
Test2 *test2 = [[Test2 alloc] init];
if ([test2 respondsToSelector:sel]) {
[anInvocation invokeWithTarget:test2];
}
}
@end
#import <Foundation/Foundation.h>
@interface Test2 : NSObject
- (void)instanceMethod;
@end
#import "Test2.h"
@implementation Test2
- (void)instanceMethod {
NSLog(@"消息轉(zhuǎn)發(fā)到這...");
}
@end