消息:消息直到運行時才會與方法實現(xiàn)進行綁定温自。在OC中方法調(diào)用是一個消息發(fā)送的過程。
在OC中調(diào)用一個方法的格式如:
[cat eat: fish];
在方法調(diào)用的時候知给,runtime會將上面的方法調(diào)用轉(zhuǎn)化成一個C語言的函數(shù)調(diào)用瓤帚,表示朝著cat發(fā)了一個eat:消息,并傳入了fish這個參數(shù):
objc_msgSend(cat, @selector(eat:), fish);
那么在這個C語言函數(shù)中發(fā)生了什么事情涩赢?編譯器是如何找到這個方法的呢戈次?消息發(fā)送的主要步驟如下:
1.首先檢查這個selector是不是要忽略。比如Mac OS X開發(fā)筒扒,有了垃圾回收就不會理會retain怯邪,release這些函數(shù)。
2.檢測這個selector的target是不是nil花墩,OC允許我們對一個nil對象執(zhí)行任何方法不會Crash悬秉,因為運行時會被忽略掉。
3.如果上面兩步都通過了冰蘑,就開始查找這個類的實現(xiàn)IMP和泌,先從cache里查找,如果找到了就運行對應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼祠肥。
4.如果cache中沒有找到就找類的方法列表中是否有對應(yīng)的方法武氓。
5.如果類的方法列表中找不到就到父類的方法列表中查找,一直找到NSObject類為止仇箱。
6.如果還是沒找到就要開始進入動態(tài)方法解析聋丝,后面會說
在消息的傳遞中,編譯器會根據(jù)情況在objc_msgSend 工碾、objc_msgSend_stret 弱睦、objc_msgSendSuper、objc_msgSendSuper_stret 這四個方法中選擇一個調(diào)用渊额。如果消息是傳遞給父類况木,那么會調(diào)用名字帶有super的函數(shù),如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時旬迹,會調(diào)用名字帶有stret的函數(shù)火惊。
動態(tài)方法解析
當(dāng)runtime系統(tǒng)在Cache和類的方法列表(包括父類)中找不到要執(zhí)行的方法時runtime會調(diào)用resolveInstanceMethod: 或者resolveClassMethod: 來給我們一次動態(tài)添加方法實現(xiàn)的機會。我們需要用class_addMethod函數(shù)完成向特定類添加特定方法實現(xiàn)的操作:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@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
上面的例子為 resolveThisMethodDynamically 這個方法添加了實現(xiàn)內(nèi)容奔垦,就是dynamicMethodIMP方法中的代碼屹耐。其中"v@:"表示返回值跟參數(shù)。注意:動態(tài)方法解析是在上面說的第6步后椿猎,消息轉(zhuǎn)發(fā)機制侵入之前執(zhí)行惶岭,動態(tài)方法解析器將會首先給予提供該方法選擇器對應(yīng)的IMP的機會寿弱。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機制,就讓resolveInstanceMethod:方法返回NO按灶。
消息轉(zhuǎn)發(fā)
1.重定向
消息轉(zhuǎn)發(fā)機制執(zhí)行前症革,runtime系統(tǒng)允許我們替換消息的接收者為其他對象。通過- (id)forwardingTargetForSelector:(SEL)aSelector方法鸯旁。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
如果此方法返回的是nil 或者self,則會進入消息轉(zhuǎn)發(fā)機制(forwardInvocation:)噪矛,否則將會向返回的對象重新發(fā)送消息。
2.轉(zhuǎn)發(fā)
當(dāng)動態(tài)方法解析不做處理返回NO時铺罢,則會觸發(fā)消息轉(zhuǎn)發(fā)機制艇挨。這時forwardInvocation:方法會被執(zhí)行,我們可以重寫這個方法來自定義我們的轉(zhuǎn)發(fā)邏輯:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
唯一參數(shù)是個NSInvocation類型對象韭赘,該對象封裝了原始的消息和消息的參數(shù)缩滨,我們可以實現(xiàn)forwardInvocation:方法來對不能處理的消息做一些處理。也可以將消息轉(zhuǎn)發(fā)給其它對象處理辞居,而不拋出錯誤楷怒。
注意:參數(shù)anInvocation是從哪來的?在forwardInvocation:消息發(fā)送前瓦灶,runtime系統(tǒng)會向?qū)ο蟀l(fā)送methodSignatureForSelector:消息鸠删,并取到返回的方法簽名用于生成NSInvocation對象。所以重寫forwardInvocation:的同時也要重寫methodSignatureForSelector:方法贼陶,否則會拋出異常刃泡。當(dāng)一個對象由于沒有相應(yīng)的方法實現(xiàn)而無法響應(yīng)某個消息時,運行時系統(tǒng)將通過forwardInvocation:消息通知該對象碉怔。每個對象都繼承了forwardInvocation:方法烘贴,我們可以將消息轉(zhuǎn)發(fā)給其它的對象。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [someObject methodSignatureForSelector:selector];
}
return signature;
}
forwardInvocation:方法就是一個不能識別消息的分發(fā)中心撮胧,將這些不能識別的消息轉(zhuǎn)發(fā)給不同的消息對象桨踪,或者轉(zhuǎn)發(fā)給同一個對象,再或者將消息翻譯成另外的消息芹啥,亦或者簡單的“吃掉”某些消息锻离,因此沒有響應(yīng)也不會報錯。例如:我們可以為了避免直接閃退墓怀,可以當(dāng)消息沒法處理時在這個方法中給用戶一個提示汽纠,也不失為一種友好的用戶體驗。
轉(zhuǎn)發(fā)和多繼承
轉(zhuǎn)發(fā)和繼承相似傀履,可用于為OC編程添加一些多繼承的效果虱朵,一個對象把消息轉(zhuǎn)發(fā)出去,就好像他把另一個對象中放法接過來或者“繼承”一樣。消息轉(zhuǎn)發(fā)彌補了objc不支持多繼承的性質(zhì)碴犬,也避免了因為多繼承導(dǎo)致單個類變得臃腫復(fù)雜絮宁。
雖然轉(zhuǎn)發(fā)可以實現(xiàn)繼承功能,但是NSObject還是必須表面上很嚴(yán)謹(jǐn)翅敌,像respondsToSelector:和isKindOfClass:這類方法只會考慮繼承體系羞福,不會考慮轉(zhuǎn)發(fā)鏈惕蹄。
方法中的隱藏參數(shù)
我們經(jīng)常用到關(guān)鍵字self,但是self 是如何獲取當(dāng)前方法的對象呢蚯涮?其實這也是runtime系統(tǒng)的作用,self是在方法運行時被動態(tài)傳入的卖陵。
當(dāng)objc_msgSend找到方法對應(yīng)實現(xiàn)時遭顶,它將直接調(diào)用該方法實現(xiàn),并將消息中的所有參數(shù)都傳遞給方法實現(xiàn)泪蔫,同時它還將傳遞兩個隱藏參數(shù):
1.接受消息的對象(self所指向的內(nèi)容棒旗,當(dāng)前方法的對象指針)
2.方法選擇器(_cmd指向的內(nèi)容,當(dāng)前方法的SEL指針)
因為在源代碼方法的定義中撩荣,我們并沒有發(fā)現(xiàn)這兩個參數(shù)的聲明铣揉,它們是在代碼被編譯時被插入方法實現(xiàn)中的。盡管這些參數(shù)沒有被明確的聲明餐曹,在源代碼中我們?nèi)匀豢梢砸盟鼈儭?/p>
這兩個參數(shù)中self更實用逛拱,它是在方法實現(xiàn)中訪問消息接收者對象的實例變量的途徑。這時我們可能會想到另一個關(guān)鍵字super,實際上super關(guān)鍵字接收到消息時台猴,編譯器會創(chuàng)建一個objc_super結(jié)構(gòu)體:
struct objc_super { id receiver; Class class; };
這個結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定的父類朽合,receiver仍然是self本身,當(dāng)我們想通過[super class]獲取父類時饱狂,編譯器其實是將指向self的id指針和class 的SEL傳遞給了objc_msgSendSuper 函數(shù)曹步。只有在NSObject類中才能找到class 方法,然后class方法底層被轉(zhuǎn)化為object_getClass()休讳,接著底層編譯器將代碼轉(zhuǎn)換為objc_msgSend(objc_super->receiver, @selector(class)) 讲婚,傳入的第一個參數(shù)是指向self的id指針,與調(diào)用[self class]相同俊柔,所以我們得到的永遠(yuǎn)都是self d的類型筹麸。因此:
// 這句話不能獲取父類的類型,只能獲取當(dāng)前類的類型名
NSLog(@"%@", NSStringFromClass([super class]));