Objective-C 是一個動態(tài)語言,可以通過運行時系統(tǒng)來動態(tài)得創(chuàng)建類和對象吕世、進行消息傳遞和轉(zhuǎn)發(fā)彰触。
在Objective-C中,[object run]語法并不會立即執(zhí)行 run 這個方法的代碼命辖。它是在運行時給 object 發(fā)送一條叫 run 的消息况毅。這個消息分蓖,也許會由 object 來處理,也許會被轉(zhuǎn)發(fā)給另一個對象尔许,或者不予理睬假裝沒收到這個消息么鹤。多條不同的消息也可以對應(yīng)同一個方法實現(xiàn)。這些都是在程序運行的時候決定的味廊。
事實上蒸甜,在編譯時你寫的 Objective-C 函數(shù)調(diào)用的語法都會被翻譯成一個 C 的函數(shù)調(diào)用objc_msgSend()
比如,下面兩行代碼就是等價的:
[array insertObject:foo atIndex:5];
objc_msgSend(array,@selector(insertObject:atIndex:), foo, 5)
在 Objective-C 中余佛,類柠新、對象和方法都是一個 C 的結(jié)構(gòu)體,從objc/objc.h頭文件中辉巡,我們可以找到他們的定義:
structobjc_class {
Class isa? OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class??????????????????????????????????????? OBJC2_UNAVAILABLE;
constchar*name ??????????????????????????????????????? OBJC2_UNAVAILABLE;
longversion ??????????????????????????????????????????? OBJC2_UNAVAILABLE;
longinfo??????????????????????????????????????????????? OBJC2_UNAVAILABLE;
longinstance_size ????????????????????????????????????? OBJC2_UNAVAILABLE;
structobjc_ivar_list *ivars ??????????????????????????? OBJC2_UNAVAILABLE;
structobjc_method_list **methodLists??????????????????? OBJC2_UNAVAILABLE;
structobjc_cache *cache ??????????????????????????????? OBJC2_UNAVAILABLE;
structobjc_protocol_list *protocols ??????????????????? OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
structobjc_object {
Class isa? OBJC_ISA_AVAILABILITY;
};
OBJC2_UNAVAILABLE;
structobjc_method_list {
structobjc_method_list *obsolete??????????????????????? OBJC2_UNAVAILABLE;
intmethod_count ??????????????????????????????????????? OBJC2_UNAVAILABLE;
#ifdef __LP64__
intspace??????????????????????????????????????????????? OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
structobjc_method method_list[1]??????????????????????? OBJC2_UNAVAILABLE;
}
structobjc_method {
SELmethod_name????????????????????????????????????????? OBJC2_UNAVAILABLE;
char*method_types ????????????????????????????????????? OBJC2_UNAVAILABLE;
IMPmethod_imp ????????????????????????????????????????? OBJC2_UNAVAILABLE;
}
在這里可以看到恨憎,在一個類中,有父類的指針郊楣,類名憔恳,版本的信息。
ivars是objc_ivar_list成員變量列表的指針净蚤;methodLists是指向objc_method_list指針的指針钥组。*methodLists是指向方法列表的指針。
objc_method_list本質(zhì)是一個有objc_method元素的可變長度的數(shù)組塞栅。一個objc_method結(jié)構(gòu)體中有函數(shù)名者铜,也就是SEL腔丧,有表示函數(shù)類型的字符串?放椰,以及函數(shù)的實現(xiàn)IMP。
從這些定義中可以看出發(fā)送一條消息也就objc_msgSend做了什么事愉粤。舉objc_msgSend(obj, run)這個例子來說:
首先砾医,通過 obj 的 isa 指針找到它的 class ;
在 class 的 method list 找 run ;
如果 class 中沒到 run,繼續(xù)往它的 superclass 中找 ;
一旦找到 run 這個函數(shù)衣厘,就去執(zhí)行它的實現(xiàn)IMP.
再找到 run 之后如蚜,把 run 的method_name作為 key ,method_imp作為 value 給存到objc_cache影暴。當(dāng)再次收到 run 消息的時候错邦,可以直接在 cache 里找到,避免去遍歷objc_method_list.
動態(tài)方法解析和轉(zhuǎn)發(fā)
在上面的例子中型宙,如果run沒有找到會發(fā)生什么撬呢?
當(dāng)向someObject發(fā)送某消息,但runtime system在當(dāng)前類和父類中都找不到對應(yīng)方法的實現(xiàn)時妆兑,runtime system并不會立即報錯使程序崩潰魂拦,而是依次執(zhí)行下列步驟:
1.Method Resolution:向當(dāng)前類發(fā)送resolveInstanceMethod:信號毛仪,檢查是否動態(tài)向該類添加了方法。
2.Fast forwarding:檢查該類是否實現(xiàn)了forwardingTargetForSelector:方法芯勘,若實現(xiàn)了則調(diào)用這個方法箱靴。若該方法返回值對象非nil或非self,則向該返回對象重新發(fā)送消息荷愕。
3.Normal Fowarding:runtime發(fā)送methodSignatureForSelector:消息獲取Selector對應(yīng)的方法簽名衡怀。返回值非空則通過forwardInvocation:轉(zhuǎn)發(fā)消息,返回值為空則向當(dāng)前對象發(fā)送doesNotRecognizeSelector:消息安疗,程序崩潰退出
Method Resolution
首先狈癞,Objective-C 運行時會調(diào)用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機會提供一個函數(shù)實現(xiàn)茂契。如果你添加了函數(shù)并返回 YES蝶桶, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程卸留。以run為例会放,你可以這么實現(xiàn):
voidrun (idself,SEL_cmd){
NSLog(@"%@ %s",self,sel_getName(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel ==@selector(run)) {
class_addMethod(self,sel, (IMP)run,"v@:");
returnYES;
}
return[superresolveClassMethod:sel];
}
如果 resolve 方法返回 NO 节沦,運行時就會移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)
Fast forwarding
如果目標(biāo)對象實現(xiàn)了-forwardingTargetForSelector:让腹,Runtime 這時就會調(diào)用這個方法募狂,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會衫嵌。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if([doctorrespondsToSelector:aSelector]) {
returndoctor;
}
return[superforwardingTargetForSelector:aSelector];
}
只要這個方法返回的不是 nil 和 self晃听,整個消息發(fā)送的過程就會被重啟唇聘,當(dāng)然發(fā)送的對象會變成你返回的那個對象璧亚。否則讨韭,就會繼續(xù) Normal Fowarding 。
Normal forwarding
這一步是 Runtime 最后一次給你挽救的機會癣蟋。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型透硝。如果-methodSignatureForSelector:返回 nil ,Runtime 則會發(fā)出-doesNotRecognizeSelector:消息疯搅,程序這時也就掛掉了濒生。如果返回了一個函數(shù)簽名,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象幔欧。
NSInvocation 實際上就是對一個消息的描述罪治,包括selector 以及參數(shù)等信息。所以你可以在-forwardInvocation:里修改傳進來的 NSInvocation 對象礁蔗,然后發(fā)送-invokeWithTarget:消息給它觉义,傳進去一個新的目標(biāo):
- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel{
return[NSMethodSignaturesignatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation*)invocation{
[invocationinvokeWithTarget:self.object];
}
這里解釋一下"v@:”,每一個方法會默認(rèn)隱藏兩個參數(shù)浴井,self晒骇、_cmd,self代表方法調(diào)用者,_cmd代表這個方法的SEL厉碟,簽名類型就是用來描述這個方法的返回值喊巍、參數(shù)的,v代表返回值為void箍鼓,@表示self崭参,:表示_cmd。
總結(jié)
Objective-C 中給一個對象發(fā)送消息會經(jīng)過以下幾個步驟:
在對象類的method_list中嘗試找到該消息款咖。如果找到了何暮,跳到相應(yīng)的函數(shù)IMP去執(zhí)行實現(xiàn)代碼;
如果沒有找到铐殃,Runtime 會發(fā)送+resolveInstanceMethod:或者+resolveClassMethod:嘗試去 resolve 這個消息海洼;
如果 resolve 方法返回 NO,Runtime 就發(fā)送-forwardingTargetForSelector:允許你把這個消息轉(zhuǎn)發(fā)給另一個對象富腊;
如果沒有新的目標(biāo)對象返回坏逢, Runtime 就會發(fā)送-methodSignatureForSelector:和-forwardInvocation:消息。你可以發(fā)送-invokeWithTarget:消息來手動轉(zhuǎn)發(fā)消息或者發(fā)送-doesNotRecognizeSelector:拋出異常赘被。