(一)對象的消息傳遞機制 objc_msgSend()
這叫做“給某個對象發(fā)送某條消息”扑庞。消息有“名稱”或“選擇子(selector)”之說。消息可以接受參數(shù),而且還可以有返回值。
id returnValue = [someObject messgeName:parameter];
本例中,someObject叫做方法調用者守屉,也叫做接受者(receiver)。messageName:是方法名蒿辙,也叫做選擇子(selector)拇泛。選擇子與參數(shù)合起來叫做”消息“(message)。在運行時思灌,編譯器會把上面這個格式的方法調用轉化為一條標準的C語言函數(shù)調用俺叭,該函數(shù)就是鼎鼎有名的objc_msgSend(),該函數(shù)是消息objc里在運行時傳遞機制中的核心函數(shù)泰偿,其原型如下:
void objc_msgSend(id self, SEL cmd, ...)
多個參數(shù)按照順序排列
根據(jù)原型轉換后:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)
執(zhí)行這條語句的時候熄守,向一個對象發(fā)送消息時,首先會在對象類的cache,method list以及父類對象的cache,method list依次查找SEL對應的IMP
(cache文末有解釋-方法緩存)
如果沒有找到耗跛,并且實現(xiàn)了動態(tài)方法決議機制就會決議裕照。如果沒有實現(xiàn)動態(tài)決議機制或者決議失敗且實現(xiàn)了消息轉發(fā)機制。就會進入消息轉發(fā)流程调塌。否則程序Crash.
消息具體傳遞流程:
objc_msgSend()函數(shù)會依據(jù)接受者(調用方法的對象)的類型和選擇子(方法名)來調用適當?shù)姆椒ā?/p>
- 接收者會根據(jù)isa指針找到接收者自己所屬的類晋南,然后在所屬類的”方法列表“(method list)中從上向下遍歷。如果能找到與選擇子名稱相符的方法羔砾,就根據(jù)IMP指針跳轉到方法的實現(xiàn)代碼负间,調用這個方法的實現(xiàn)紊扬。
- 如果找不到與選擇子名稱相符的方法,接收者會根據(jù)所屬類的superClass指針唉擂,沿著類的繼承體系繼續(xù)向上查找(向父類查找),如果能找到與名稱相符的方法檀葛,就根據(jù)IMP指針跳轉到方法的實現(xiàn)代碼玩祟,調用這個方法的實現(xiàn)。
- 如果在繼承體系中還是找不到與選擇子相符的方法屿聋,此時就會執(zhí)行”消息轉發(fā)(message forwarding)“操作空扎。
(二)消息轉發(fā)流程
消息轉發(fā)分為兩個階段。第一階段叫做“動態(tài)方法解析(dynamic method resolution)”润讥,或者叫“動態(tài)方法決議”转锈。第二階段涉及到“完整的消息轉發(fā)機制(full forwarding mechanism)”,或者叫“完整的消息轉發(fā)原理”楚殿。
-
動態(tài)方法決議
字面上我的理解是:消息在發(fā)送過程中進行判斷到底這消息該由誰接收
一:詢問是否有動態(tài)添加方法來進行處理
Objective C 提供了一種名為動態(tài)方法決議的手段撮慨,使得我們可以在運行時動態(tài)地為一個 selector 提供實現(xiàn)。我們只要實現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveClassMethod:(SEL)selector;**
具體代碼:
//People.m
void speak(id self, SEL _cmd){
NSLog(@"Now I can speak.");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));
if (sel == @selector(speak)) {
class_addMethod([self class], sel, (IMP)speak, "V@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)name
{
NSLog(@" >> Class resolving %@", NSStringFromSelector(name));
return [super resolveClassMethod:name];
}
-
完整的消息轉發(fā)
完整的消息轉發(fā)又分為兩個階段脆粥,第一階段稱為備援接受者(replacement receiver)砌溺,第二階段才是啟動完整的消息轉發(fā)機制。
1.備援接收者
詢問別人是否可以幫助實現(xiàn)一下
(id)forwardingTargetForSelector:(SEL)selector;
具體代碼:
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector: %@", NSStringFromSelector(aSelector));
Bird *bird = [[Bird alloc] init];
if ([bird respondsToSelector:aSelector]) {
return bird;
}
return [super forwardingTargetForSelector: aSelector];
}
// Bird.m
- (void)fly {
NSLog(@"I am a bird, I can fly.");
}
方法參數(shù)代表未知的選擇子变隔,返回值為備援接受者规伐,若當前接受者能找到備援接受者,就直接返回匣缘,這個未知的選擇子將會交由備援接受者處理猖闪。如果找不到備援接受者,就返回nil肌厨,此時就會啟用“完整的消息轉發(fā)機制”培慌。
2.完整的消息轉發(fā)
- (void)forwardInvocation:(NSInvocation *)invocation;
核心實現(xiàn)代碼:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation: %@", NSStringFromSelector([anInvocation selector]));
if ([anInvocation selector] == @selector(code)) {
Monkey *monkey = [[Monkey alloc] init];
[anInvocation invokeWithTarget:monkey];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"method signature for selector: %@", NSStringFromSelector(aSelector));
if (aSelector == @selector(code)) {
return [NSMethodSignature signatureWithObjCTypes:"V@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
看完核心代碼還覺得慌,一臉霧水柑爸,具體怎么用呢检柬?代碼怎么寫?寫在哪里竖配?一切都準備在后面何址,不要慌,繼續(xù)往下看进胯,待著疑問看下去
實現(xiàn)此方法時用爪,如果發(fā)現(xiàn)調用操作不應該由本類處理,則需要沿著繼承體系胁镐,調用父類的同名方法偎血,這樣一來诸衔,繼承體系中的每個類都有機會處理這個調用請求,直至rootClass颇玷,也就是NSObject類笨农。如果最后調用了NSObject的類方法,那么該方法還會繼而調用”doesNotRecognizeSelector:“以拋出異常帖渠,此異常表明選擇子最終也未能得到處理谒亦。消息轉發(fā)到此結束。
最后消息未能處理的時候空郊,還會調用到
- (void)doesNotRecognizeSelector:(SEL)aSelector
我們也可以在這個方法中做些文章份招,避免掉crash,但是只建議在線上環(huán)境的時候做處理狞甚,實際開發(fā)過程中還要把異常拋出來锁摔。
完整消息轉發(fā)實例代碼:
代碼例子:
viewController中創(chuàng)建TestModel
TestModel * model = [[TestModel alloc]init];
[model testMethod];
TestModel.h
-(void)testMethod;
TestModel.m
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(testMethod))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if (anInvocation.selector == @selector(testMethod))
{
TestModelHelper1 *h1 = [[TestModelHelper1 alloc] init];
TestModelHelper2 *h2 = [[TestModelHelper2 alloc] init];
[anInvocation invokeWithTarget:h1];
[anInvocation invokeWithTarget:h2];
}
}
@implementation TestModelHelper1
-(void)testMethod
{
NSLog(@"i am TestModelHelper1");
}
@end
@implementation TestModelHelper2
-(void)testMethod
{
NSLog(@"i am TestModelHelper2");
}
@end
運行可看到消息轉發(fā)日志
ps:方法緩存
通篇下來,發(fā)現(xiàn)調用一個方法并不像我們想的那么簡單哼审,更不像我們寫的那么簡單谐腰,一個方法的執(zhí)行其實底層需要很多步驟。正因如此涩盾,objc_msgSend()會將調用過且匹配到的方法緩存在”快速映射表(fast map)“中怔蚌,快速映射表就是方法的緩存表。每個類都有這樣一個緩存旁赊。所以桦踊,即便子類實例從父類的方法列表中取過了某個對象方法,那么子類的方法緩存表中也會緩存父類的這個方法终畅,下次調用這個方法籍胯,會優(yōu)先去當前類(對象所屬的類)的方法緩存表中查找這個方法,這樣的好處是顯而易見的离福,減少了漫長的方法查找過程杖狼,使得方法的調用更快。同樣妖爷,如果父類實例對象調用了同樣的方法蝶涩,也會在父類的方法緩存表中緩存這個方法。
同理絮识,如果用一個子類對象調用某個類方法绿聘,也會在子類的metaclass里緩存一份。而當用一個父類對象去調用那個類方法的時候次舌,也會在父類的metaclass里緩存一份熄攘。