-
objc_msgSend
OC中的實(shí)例對(duì)象調(diào)用一個(gè)方法稱作消息傳遞,例如:
Ink *inkInstance = [[Ink alloc] init];
[inkInstance sendMessage:@"i like beautiful girl"];
代碼中,我們將inkInstance這個(gè)實(shí)例對(duì)象稱之為消息接收者,sendMessage:我們稱之為選擇子,即所謂的selector,selector和參數(shù)共同構(gòu)成消息,所以
[inkInstance sendMessage:@"i like beautiful girl"]
可以理解為將消息:"發(fā)送一個(gè)消息: i like beautiful girl"發(fā)送給消息的接受者inkInstance。
OC中里的消息傳遞采用動(dòng)態(tài)綁定機(jī)制來(lái)決定具體調(diào)用哪個(gè)方法,OC的實(shí)例方法在通過(guò)runtime轉(zhuǎn)寫為C語(yǔ)言后實(shí)際就是一個(gè)函數(shù)(也就是說(shuō)runtime實(shí)際上就是將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^(guò)程的結(jié)構(gòu)體),但是OC并不是在編譯期決定調(diào)用哪個(gè)函數(shù),而是在運(yùn)行期決定,因?yàn)榫幾g期根本不能確定最終會(huì)調(diào)用哪個(gè)函數(shù),這是由于運(yùn)行期可以修改方法的實(shí)現(xiàn)。例如:
id p= @1024;
//輸出1024
NSLog(@"%@", p);
//程序崩潰,報(bào)錯(cuò)[__NSCFNumber appendString:]: unrecognized selector ...
[p appendString:@"I'm a good boy"];
這段代碼在編譯期沒(méi)有任何問(wèn)題,因?yàn)閕d類型可以指向任何類型的實(shí)例對(duì)象,NSString有一個(gè)方法appendString:,在編譯期不確定這個(gè)p到底具體指代什么類型的實(shí)例對(duì)象,并且在運(yùn)行期還可以給NSNumber類型添加新的方法,因此編譯期發(fā)現(xiàn)有appendString:的函數(shù)聲明就不會(huì)報(bào)錯(cuò),但在運(yùn)行時(shí)找不到在NSNumber類中找不到appendString:方法,就會(huì)報(bào)錯(cuò)。這也就是消息傳遞的強(qiáng)大之處和弊端,編譯期無(wú)法檢查到未定義的方法,運(yùn)行期可以添加新的方法。
而OC則是通過(guò)強(qiáng)大的runtime將這些方法轉(zhuǎn)換為C語(yǔ)言的函數(shù),但是是如何調(diào)用這些函數(shù)的呢,這里就將說(shuō)道我們所說(shuō)的objc_msgSend。(注:objc/msgSend 只有對(duì)象才能發(fā)送消息,因此以objc開頭 導(dǎo)入 #import <objc/message.h> 或者直接導(dǎo)入 #import <objc/runtime.h> 注意 Xcode 6 之后代碼檢查 單獨(dú)使用<objc/message.h>會(huì)報(bào)錯(cuò) builtSeting 修改 Enable Strict Checking of objc_msgSend Calls -> NO 才能調(diào)用 objc_msgSend)
Ink *inkInstance = [Ink alloc];
inkInstance = [inkInstance init];
//為方便查看通過(guò)objc_msgSend轉(zhuǎn)寫后的的代碼,這里用作兩步來(lái)執(zhí)行alloc init方法
[inkInstance sendMessage:@"i like beautiful girl"];
通過(guò)objc_msgSend即可轉(zhuǎn)譯為
Ink *inkInstance = objc_msgSend(objc_getClass("Ink"),sel_registerName("alloc"));
這行代碼達(dá)到了如下幾個(gè)效果,第一獲取Ink類,第二注冊(cè)alloc方法,第三發(fā)送消息,將消息alloc發(fā)送給類對(duì)象,可以簡(jiǎn)單的將注冊(cè)方法理解為,通過(guò)方法名獲取到轉(zhuǎn)寫后C語(yǔ)言函數(shù)的函數(shù)指針。
inkInstance = objc_msgSend(inkInstance,sel_registerName("init"));
這一行則是,注冊(cè)了init方法,然后通過(guò)objc_msgSend函數(shù)將消息init發(fā)送給消息的接受者inkInstance。
objc_msgSend(inkInstance,sel_registerName("sendMessage:"),@"i like beautiful girl");
這一行代碼同樣是先注冊(cè)方法sendMessage:然后通過(guò)objc_msgSend函數(shù)將消息sendMessage:發(fā)送給消息的接收者,只是多了一個(gè)參數(shù)的傳遞。
到這里,我們應(yīng)該就可以看出OC的runtime通過(guò)objc_msgSend函數(shù)將一個(gè)面向?qū)ο蟮南鬟f轉(zhuǎn)為了面向過(guò)程的函數(shù)調(diào)用。objc_msgSend函數(shù)根據(jù)消息的接受者和selector選擇適當(dāng)?shù)姆椒▉?lái)調(diào)用。這里就涉及到OC的runtime是如何將面向?qū)ο蟮念愑成錇槊嫦蜻^(guò)程的結(jié)構(gòu)體的,我們可以看一下幾個(gè)重要結(jié)構(gòu)體的定義:
文件objc/runtime.h中有如下定義:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
}
/* Use `Class` instead of `struct objc_class *` */
文件objc/objc.h文件中有如下定義
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
注意結(jié)構(gòu)體struct objc_class中包含一個(gè)成員變量struct objc_method_list **methodLists,通過(guò)名稱我們分析出這個(gè)成員變量保存了實(shí)例方法列表。
結(jié)構(gòu)體struct objc_method_list里面包含以下幾個(gè)成員變量:結(jié)構(gòu)體struct _objc_method的大小、方法個(gè)數(shù)以及最重要的方法列表,方法列表存儲(chǔ)的是方法描述結(jié)構(gòu)體struct _objc_method,該結(jié)構(gòu)體里保存了選擇子、方法類型以及方法的具體實(shí)現(xiàn)??梢钥闯龇椒ǖ木唧w實(shí)現(xiàn)就是一個(gè)函數(shù)指針,也就是我們自定義的實(shí)例方法,選擇子也就是selector可以理解為是一個(gè)字符串類型的名稱,用于查找對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)
。
這樣就能解釋objc_msgSend的工作原理的:
首先,通過(guò)接收者的isa指針找到它的class ;
在class的
struct objc_method_list找對(duì)應(yīng)的方法 ;如果class中沒(méi)有找到,繼續(xù)往它的
superclass中找 ;一旦找到對(duì)應(yīng)的這個(gè)函數(shù),就去執(zhí)行它的實(shí)現(xiàn)
IMP.
如果到了繼承樹的根部(通常為NSObject)還沒(méi)有找到,那就會(huì)調(diào)用NSObjec的一個(gè)方法doesNotRecognizeSelector:,這個(gè)方法就會(huì)報(bào)unrecognized selector錯(cuò)誤
而為了避免每次搜索和靜態(tài)綁定那樣直接跳轉(zhuǎn)到函數(shù)指針指向的位置去執(zhí)行,類對(duì)象也就是結(jié)構(gòu)體struct objc_class中有一個(gè)成員變量struct objc_cache,這個(gè)緩存里緩存的正是搜索方法的匹配結(jié)果,這樣在第二次及以后再訪問(wèn)時(shí)就可以采用映射的方式找到相關(guān)實(shí)現(xiàn)的具體位置。-
動(dòng)態(tài)綁定(所屬類動(dòng)態(tài)方法解析)
如果沿繼承樹沒(méi)有搜索到相關(guān)方法則會(huì)向接收者所屬的類進(jìn)行一次請(qǐng)求,看是否能夠動(dòng)態(tài)的添加一個(gè)方法,注意這是一個(gè)類方法,因?yàn)槭窍蚪邮照咚鶎俚念愡M(jìn)行請(qǐng)求。這里就涉及到兩個(gè)方法:
//通過(guò)類對(duì)象調(diào)用的未實(shí)現(xiàn)的方法則會(huì)執(zhí)行此方法
+ (BOOL)resolveClassMethod:(SEL)sel
//通過(guò)實(shí)例對(duì)象調(diào)用的未實(shí)現(xiàn)的方法則會(huì)執(zhí)行此方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
舉個(gè)??
調(diào)用代碼
//Ink類中即沒(méi)有聲明也沒(méi)有實(shí)現(xiàn)receiveSomething和classMethod這兩個(gè)方法
Ink *inkInstance = [[Ink alloc]init];
//調(diào)用實(shí)例方法
[inkInstance performSelector:@selector(receiveSomething:) withObject:@"調(diào)用了實(shí)例方法 receiveSomething"];
//調(diào)用了類方法
[Ink performSelector:@selector(classMethod:) withObject:@"調(diào)用了類方法 classMethod"];
處理代碼
#import "Ink.h"
#import<objc/runtime.h>
@implementation Ink
void messageSend (id self,SEL _cmd,id object) {
if(strcmp(sel_getName(_cmd), "receiveSomething:") == 0) {
NSLog(@"message for appendString:%@",object);
}else if (strcmp(sel_getName(_cmd), "classMethod:") == 0) {
NSLog(@"message for classMethod:%@",object);
}
}
#pragma mark 第一步驟(所屬類動(dòng)態(tài)方法解析)
//實(shí)例調(diào)用方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(receiveSomething:)) {
class_addMethod(self, sel, (IMP)messageSend, "v@:*");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(classMethod:)) {
class_addMethod(object_getClass([self class]), sel, (IMP)messageSend, "v@:*");
return YES;
}
return [super resolveClassMethod:sel];
}
由于Ink類沒(méi)有聲明和定義receiveSomething:和classMethod:方法,所以運(yùn)行時(shí)應(yīng)該會(huì)報(bào)unrecognized selector錯(cuò)誤,但是并沒(méi)有。
對(duì)于receiveSomething:
我們重寫了類方法+ (BOOL)resolveInstanceMethod:(SEL)sel,當(dāng)找不到相關(guān)實(shí)例方法的時(shí)候就會(huì)調(diào)用該類方法去詢問(wèn)是否可以動(dòng)態(tài)添加。
對(duì)于classMethod:
我們重寫了類方法+ (BOOL)resolveClassMethod:(SEL)sel,當(dāng)找不到相關(guān)實(shí)例方法的時(shí)候就會(huì)調(diào)用該類方法去詢問(wèn)是否可以動(dòng)態(tài)添加。
如果返回True就會(huì)再次執(zhí)行相關(guān)方法,接下來(lái)看一下如何給一個(gè)類動(dòng)態(tài)添加一個(gè)方法,那就是調(diào)用runtime庫(kù)中的class_addMethod方法,該方法的原型是
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
通過(guò)參數(shù)名可以看出第一個(gè)參數(shù)是需要添加方法的類,第二個(gè)參數(shù)是一個(gè)selector,也就是實(shí)例方法的名字,第三個(gè)參數(shù)是一個(gè)IMP類型的變量也就是函數(shù)實(shí)現(xiàn),需要傳入一個(gè)C函數(shù),這個(gè)函數(shù)至少有兩個(gè)參數(shù),一個(gè)是id self一個(gè)是SEL _cmd,第四個(gè)參數(shù)是函數(shù)類型。這里需要注意的是,對(duì)于相關(guān)實(shí)例方法的時(shí)候,參數(shù)Class cls可以是self,即類對(duì)象,而對(duì)于找不到相關(guān)類方法的時(shí)候,參數(shù)Class cls必須為元類,即object_getClass([self class])。
-
消息轉(zhuǎn)發(fā)(所屬類動(dòng)態(tài)方法解析)
當(dāng)對(duì)象所屬類不能動(dòng)態(tài)添加方法后,runtime就會(huì)詢問(wèn)當(dāng)前的接受者是否要進(jìn)行消息轉(zhuǎn)發(fā)
-
快速轉(zhuǎn)發(fā)
//實(shí)例方法調(diào)用會(huì)走這里
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(receiveSomething:)) {
return [messages new];
}
return nil;
}
//類方法調(diào)用會(huì)走這里
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(classMethod:)) {
return [messages new];
}
return nil;
}
-
標(biāo)準(zhǔn)(慢速)轉(zhuǎn)發(fā)
//實(shí)例方法調(diào)用會(huì)走這里
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
sig = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
messages *me = [[messages alloc]init];
if ([me respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:me];
}
}
//類方法調(diào)用會(huì)走這里
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
sig = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return sig;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
messages *me = [[messages alloc]init];
if ([me respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:me];
}
}
既然forwardingTargetForSelector可以實(shí)現(xiàn)消息轉(zhuǎn)發(fā),為什么還要使用forwardInvocation作為消息管理中心呢?
- forwardingTargetForSelector使用簡(jiǎn)單,不需要重寫methodSignatureForSelector,產(chǎn)生的消耗也比f(wàn)orwardInvocation低得多。
- forwardingTargetForSelector無(wú)法獲取當(dāng)前的NSInvocation,或者說(shuō)少了一些可以操作的值。
而methodSignatureForSelector則是獲取到函數(shù)的簽名,包含了該函數(shù)的返回值以及參數(shù),只有methodSignatureForSelector返回不會(huì)為nil的時(shí)候,才會(huì)走forwardInvocation方法。
以上則為我所理解的runtime消息機(jī)制,有不理解和不足的的希望大家咨詢和補(bǔ)充。謝謝。