本位github地址 https://github.com/ICZhuang/Runtime
我們通常定義一個類的時候揩页,會直接將方法寫到類中,給予一種調(diào)用改對象方法就一定會找到該對象的方法實現(xiàn)的錯覺砚作。其實不然。我們說調(diào)用一個對象的某一個對象方法嘹锁,其實更準(zhǔn)確的描述應(yīng)該是向?qū)ο蟀l(fā)送一個消息葫录,比如[receiver message],應(yīng)該描述成像receiver這個對象發(fā)送一個message消息领猾。
objc_msgSend
在Objective-C語言中米同,消息并沒有跟方法的實現(xiàn)綁定的骇扇,直到運(yùn)行狀態(tài)下,才知道響應(yīng)消息時應(yīng)該執(zhí)行的方法面粮。比如編譯器會將下面表達(dá)式
[receiver message]
轉(zhuǎn)換成消息發(fā)送方法 objc_msgSend 方式少孝。這個方法將消息接受者和方法selector作為兩個最重要的參數(shù)
objc_msgSend(receiver, selector)
任何消息傳遞過來的參數(shù)都會以可變形參的形式拼接在selector后面
objc_msgSend(receiver, selector, arg1, arg2, ...)
在消息發(fā)送方法中,就是在做動態(tài)綁定
- 第一步查找selector的方法實現(xiàn)熬苍,相同的selector在不同的類有不同的實現(xiàn)稍走,但它會在 receiver的class中找;
- 然后執(zhí)行程序柴底,將receiver和其它參數(shù)作為實參傳遞過去婿脸;
- 最后將程序的返回值作為自己的返回值返回
查找的key就存在類的結(jié)構(gòu)中。類結(jié)構(gòu)中有兩個重要的元素:
- superclass 指針
- 分發(fā)表(dispatch table)似枕,這個分發(fā)表將selector和方法的實現(xiàn)地址關(guān)聯(lián)了起來
當(dāng)一個對象被創(chuàng)建盖淡,分配內(nèi)存年柠,并且給成員變量做初始化凿歼。第一個變量就是isa指針,指向它的class冗恨。通過class便可找到所有在繼承鏈中的類答憔。
當(dāng)給一個對象發(fā)送消息,消息發(fā)送方法就會沿著對象的isa指針在類的分發(fā)表里找方法的selector掀抹,如果在類中沒有找到虐拓,那么繼續(xù)在superclass的分發(fā)表里找,直到查找到NSObject class傲武。一旦查找到蓉驹,消息發(fā)送方法就調(diào)起該方法實現(xiàn)并把對象的數(shù)據(jù)結(jié)構(gòu)傳遞過去。
為了提高查找速度揪利,Runtime 會緩存那些使用過的方法态兴。每個class都有一個cache,它可以緩存繼承而來的方法和自身定義的方法疟位。在從分發(fā)表里查找之前瞻润,消息查找機(jī)制會先從cache里查找。這個緩存會動態(tài)的增長以存放新的消息指定執(zhí)行過的方法甜刻。
隱式參數(shù)
當(dāng)objc_msgSend找到了方法的實現(xiàn)绍撞,它就會調(diào)起實現(xiàn),并把消息傳遞的參數(shù)全部傳遞過去得院,其中包括兩個默認(rèn)就會傳遞的參數(shù)
- 消息接收對象
- method對應(yīng)的selector
通常在寫Objective-C代碼的時候傻铣,通常會省去這兩個參數(shù),當(dāng)被編譯的時候祥绞,參會插入到方法實現(xiàn)中非洲。雖然在編碼時不顯示聲明阱驾,但我們?nèi)匀豢梢詰?yīng)用到它們。?self 則為消息接收對象怪蔑, _cmd 則為消息的 selector里覆。比如下面示例,_cmd 是strange的selector缆瓣,self 則是接收strange消息的接受者
- strange {
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
方法地址
唯一可以避開動態(tài)綁定的方法就是獲得方法(實現(xiàn))的地址喧枷,直接調(diào)用。一個方法被多次連續(xù)調(diào)用弓坞,而又想避開每次都要查找的消耗隧甚,可以用這種方法避開動態(tài)綁定。
通過NSObject class中聲明的 methodForSelector: 可以獲得方法的地址(IMP)渡冻,就是一個函數(shù)指針戚扳。通過函數(shù)指針喚起它的實現(xiàn)。但是得注意函數(shù)指針與IMP的轉(zhuǎn)換族吻,返回值和參數(shù)都需要包含到
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
前面兩個參數(shù)是用來接收 self 和 _cmd 的帽借,轉(zhuǎn)成函數(shù)形式需要加上。
注意 methodForSelector: 是 Cocoa runtime 提供的超歌,并非Objective-C語言提供的砍艾。
給一個對象發(fā)送消息后,如果對應(yīng)的方法沒找到巍举,則進(jìn)入下面步驟:
- 動態(tài)方法解析:喚起對象的resolveInstanceMethod: 或者 resolveClassMethod: 脆荷,詢問是否能夠處理消息的selector,如果不能則進(jìn)入下一步懊悯;
- 備援對象:喚起對象的forwardingTargetForSelector:蜓谋,返回能夠處理該selector的對象,如果返回nil或者self炭分,則進(jìn)入下一步桃焕;
- 消息轉(zhuǎn)發(fā):實現(xiàn)forwardInvocation:將消息轉(zhuǎn)發(fā)給其它能處理該消息的對象,該方法的參數(shù)會通過methodSignatureForSelector:創(chuàng)建欠窒,所以還應(yīng)該實現(xiàn)methodSignatureForSelector:方法
參考:理解消息傳遞機(jī)制 [Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理]
動態(tài)方法解析
有些情況你可能希望動態(tài)的提供方法實現(xiàn)覆旭。比如聲明了一個動態(tài)屬性
@dynamic propertyName;
這說明與改屬性相關(guān)的方法(setter&getter)要動態(tài)添加
可以實現(xiàn) resolveInstanceMethod: 和 resolveClassMethod: 動態(tài)的為對象或者類的selector添加實現(xiàn)。
一個Objective-C的方法其實在底層就是一個簡單的C函數(shù)岖妄,這函數(shù)帶著至少兩個參數(shù)型将,self 和 _cmd。通過class_addMethod添加一個函數(shù)作為類的方法荐虐。因此七兜,先添加一個函數(shù)
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
然后在 resolveInstanceMethod: 中動態(tài)的將它作為方法(如resolveThisMethodDynamically)添加到class中
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
消息轉(zhuǎn)發(fā)和動態(tài)方法解析是順序的,在進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制之前福扬,可以通過動態(tài)方法解析來處理方法腕铸。如果respondsToSelector: 和 instancesRespondToSelector: 被喚起惜犀,通過動態(tài)方法處理為selector提供一個IMP。如果實現(xiàn)了resolveInstanceMethod: 但又想讓selector進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制狠裹,那么在遇到該selector時返回NO虽界。
經(jīng)試驗,如果實現(xiàn)了resolveInstanceMethod: 并且在里面往類添加了selector關(guān)聯(lián)的方法實現(xiàn)涛菠,則不會再走備援對象和消息轉(zhuǎn)發(fā)機(jī)制莉御。
消息轉(zhuǎn)發(fā)
在出現(xiàn)崩潰之前,runtime 給對象發(fā)送 forwardInvocation: 消息俗冻,并將一個NSInvocation對象作為唯一的參數(shù)傳遞過去礁叔,這個NSInvocation對象囊括了最初的消息和參數(shù)。
你可以實現(xiàn) forwardInvocation: 方法提供對消息的默認(rèn)處理迄薄,或者過濾消息避免出現(xiàn)錯誤琅关。顧名思義,方法forwardInvocation:通常是用來將轉(zhuǎn)發(fā)消息給另外的對象讥蔽。
為了了解消息轉(zhuǎn)發(fā)的意圖涣易,想象這樣一種場景:假設(shè)需要設(shè)計一個對象可以響應(yīng)negotiate消息,并且希望它的響應(yīng)期間可以帶動另外一種對象響應(yīng)negotiate消息勤篮。完成這需求很簡單都毒,可以在你的negotiate方法實現(xiàn)中將消息傳遞給另外一個對象色罚。
進(jìn)一步假設(shè)碰缔,你設(shè)計的對象對negotiate的響應(yīng)實際上其實就是執(zhí)行另外一個類的實現(xiàn),其中一種方法就是讓你的類繼承自該類戳护。然而金抡,這可能并不合理。原因在于腌且,你的類和實現(xiàn)了negotiate的類可能在不同的繼承樹分支梗肝。就是說它們可能不存在直接或間接的繼承關(guān)系。
即使你不能繼承negotiate方法铺董,你可以簡單地實現(xiàn)negotiate巫击,將negotiate消息傳遞給另外一個對象。如此一來你的對象就響應(yīng)了negotiate精续,就像是從其它對象"借用"到了negotiate坝锰。
- (id)negotiate {
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
這種方法有些笨拙,特別是當(dāng)有多種消息需要傳遞給其它對象的情況重付。你需要在一個方法中覆蓋所有其它對象應(yīng)該響應(yīng)的方法顷级。此外,在寫代碼的時候确垫,有一些消息的轉(zhuǎn)發(fā)是不可預(yù)見的弓颈。這些消息是取決于runtime帽芽,它們可能被新方法替代,或被新類重新實現(xiàn)翔冀。
forwardInvocation: 提供的是一種動態(tài)解決方案导街。它工作的原理就想這樣:當(dāng)一個對象沒有一個方法可以匹配消息中的selector,這個對象是不能響應(yīng)該消息的纤子,這個時候菊匿,runtime 會給對象發(fā)送一個 forwardInvocation: 消息。所有的對象都從NSObject類中繼承了改方法计福。然而跌捆,NSObject中的該方法只是簡單的調(diào)起doesNotRecognizeSelector: 。通過重寫 forwardInvocation: 方法象颖,將消息轉(zhuǎn)發(fā)給另外的對象佩厚。
轉(zhuǎn)發(fā)一個消息,需要在forwardInvocation:方法中:
- 判斷消息應(yīng)該轉(zhuǎn)發(fā)給誰说订,還有
- 轉(zhuǎn)發(fā)的時候帶上它原始的參數(shù)
消息可以通過 invokeWithTarget: 方法發(fā)送:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
如果有返回值抄瓦,則返回值會返給最初的發(fā)送者,包括任何類型 ids, structures, and double-precision floating-point numbers.
forwardInvocation: 方法就像是未知消息的分發(fā)中心陶冷,將它們投遞到指定的接受者钙姊。由或者是中轉(zhuǎn)站,將消息發(fā)送給同一目標(biāo)埂伦。它可以將一個消息轉(zhuǎn)換成另一個消息煞额,或者簡單的就把消息"吞下",讓消息得不到響應(yīng)沾谜。forwardInvocation: 所干的事兒就看它是怎么被實現(xiàn)的膊毁。
注意:forwardInvocation: 只有在消息的接受者不能夠響應(yīng)的情況下才會去處理該消息。舉例來說基跑,如果希望一個對象轉(zhuǎn)發(fā)negotiate消息給其它的對象婚温,那么這個對象不能有negotiate方法,如果有媳否,消息永遠(yuǎn)也不可能被forwardInvocation:處理栅螟。
轉(zhuǎn)發(fā)和多繼承
如下圖,一個Warrior實例對象轉(zhuǎn)發(fā)一個negotiate消息給一個Diplomat實例對象篱竭。Warrior就好像Diplomat一樣能響應(yīng)negotiate消息
這樣看來力图,轉(zhuǎn)發(fā)消息的對象就"繼承"了來自兩個繼承樹分支中的方法 — 本類所在繼承分支和消息真正響應(yīng)的對象所在的分支。按白話說室抽,轉(zhuǎn)發(fā)消息的對象就想繼承了多個對象搪哪,2?而這些對象不存在直接或間接的繼承關(guān)系。
消息轉(zhuǎn)發(fā)提供了多繼承中的許多特性。但是晓折,他們兩這有一點不同的是:多繼承是將多個不同的行為合并到一個對象里面惑朦。它趨向更大,更豐富的對象的定義漓概。相反漾月,轉(zhuǎn)發(fā)趨向?qū)⑷蝿?wù)分割派給不同的對象。它將問題分解成不同的對象胃珍,又將這些對象以一種方式關(guān)聯(lián)起來梁肿,但這種方式對消息發(fā)送者來說是透明的。
轉(zhuǎn)發(fā)和繼承
雖說轉(zhuǎn)發(fā)很像繼承觅彰,但NSObject class卻能清楚的區(qū)分它們吩蔑。像respondsToSelector:和 isKindOfClass: 這樣的方法只會是從繼承樹種查找,并不會在轉(zhuǎn)發(fā)鏈中查找填抬。舉例來說烛芬,一個Warrior object想知道自己是否響應(yīng)negotiate消息
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
答案是NO,即使它能接收negotiate消息飒责,某種意義上赘娄,它只是轉(zhuǎn)發(fā)消息給Diplomat object。
除非宏蛉,有這樣一種情況遣臼,你希望它表現(xiàn)出來的是仿佛繼承了消息轉(zhuǎn)發(fā)的目的對象一樣,你需要重新實現(xiàn)respondsToSelector: 和 isKindOfClass:
- (BOOL)respondsToSelector:(SEL)aSelector {
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/// 這里拾并,檢測 aSelector 消息是否可以被轉(zhuǎn)發(fā)到另一個對象揍堰,或者另一個對象是否
/// 是否可以響應(yīng) aSelector 消息, 如果可以retrun YES
}
return NO;
}
如果一個對象可以轉(zhuǎn)發(fā)消息給它的代理對象辟灰,你需要像下面一樣實現(xiàn)methodSignatureForSelector:方法
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}