iOS基礎(chǔ)(八) - runtime之消息傳遞

前言:本來(lái)是想整理一下runtime相關(guān)的知識(shí)的,誰(shuí)知道,越陷越深,一個(gè)知識(shí)點(diǎn)連著一個(gè)知識(shí)點(diǎn),我怕以后忘記,所以,先記下來(lái)了,誰(shuí)叫我比較懶呢。??

1.iOS里面的函數(shù)調(diào)用其實(shí)就是消息轉(zhuǎn)發(fā)的過(guò)程。(How to prove?)

先新建一個(gè)繼承NSObject的類ClassA,里面有一個(gè)實(shí)例方法printStr:方法和類方法classMethod:,如下:

@implementation ClassA

+ (void)classMethod: (NSString *)str {
    NSLog(@"%@", str);
}

- (void)printSomething: (NSString *)str {
    NSLog(@"%@", str);
}

@end

//main.m 在main里調(diào)用
[ClassA classMethod: @"classMethod"];
ClassA *classA = [ClassA new];
[classA printStr: @"instanceMethod"];

將main.m編譯成C++源文件

clang -rewrite-objc main.m

編譯結(jié)果如下:

//類方法調(diào)用
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)objc_getClass("ClassA"), sel_registerName("classMethod:"), (NSString *)&__NSConstantStringImpl__var_folders_pn_xqk2zmnx559bmvnwm35rzb4h0000gn_T_main_2b6623_mi_0);

//實(shí)例方法調(diào)用
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)classA, sel_registerName("printStr:"), (NSString *)&__NSConstantStringImpl__var_folders_pn_xqk2zmnx559bmvnwm35rzb4h0000gn_T_main_2b6623_mi_1);

可以看出來(lái),調(diào)用類方法和實(shí)例方法最后都會(huì)轉(zhuǎn)換成objc_msgSend方法的調(diào)用,所以,objc_msgSend是何方神圣?別急,先找到objc/message.h頭文件,如下:

/** 
 * Sends a message with a simple return value to an instance of a class.
 * 
 * @param self A pointer to the instance of the class that is to receive the message.
 * @param op The selector of the method that handles the message.
 * @param ... 
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method.
 * 
 * @note When it encounters a method call, the compiler generates a call to one of the
 *  functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
 *  Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; 
 *  other messages are sent using \c objc_msgSend. Methods that have data structures as return values
 *  are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
 */
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

可以看的出來(lái),實(shí)例方法轉(zhuǎn)換成了相應(yīng)的消息傳遞。那么類方法呢?還記得我寫(xiě)的上一篇文章《聊一聊NSObject對(duì)象模型》里面的那張instance-class-metaClass之間的關(guān)系不?里面instance的isa指針指向的是class,而class的isa指針指向的是metaClass,我的理解就是可以把class看作metaClass的instance,那么類方法和實(shí)例方法的調(diào)用完全一致了。

2.消息傳遞的過(guò)程

根據(jù)蘋(píng)果官方文檔,消息轉(zhuǎn)發(fā)的過(guò)程如下(這里說(shuō)的是實(shí)例方法的調(diào)用,類方法類似):

1.通過(guò)實(shí)例的isa指針找到相應(yīng)的class。
2.在cache(這里存放的都是函數(shù)實(shí)現(xiàn),一旦函數(shù)被調(diào)用就會(huì)保存在緩存中)中以selector為key查找相應(yīng)的函數(shù)imp,若找到則直接調(diào)用,否則進(jìn)行下一步。
3.在table(這里存放的是類的所有的函數(shù)列表)中繼續(xù)尋找相應(yīng)的函數(shù)實(shí)現(xiàn),若找到則直接調(diào)用,否則進(jìn)行下一步。
4.通過(guò)class的superclass指針找到父類,回到第2步。
5.直到找到根類,還是沒(méi)有找到相應(yīng)的函數(shù)實(shí)現(xiàn),則開(kāi)始進(jìn)行消息轉(zhuǎn)發(fā)。

我們來(lái)看一下蘋(píng)果官方文檔的說(shuō)明:

When a message is sent to an object, the messaging function follows the object’s isa pointer to the class structure where it looks up the method selector in the dispatch table.
If it can’t find the selector there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table. Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class.
Once it locates the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.

To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used.
There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class.
Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again).
If the method selector is in the cache, messaging is only slightly slower than a function call.
Once a program has been running long enough to “warm up” its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.

第一段,主要講了消息轉(zhuǎn)發(fā)的整個(gè)過(guò)程,其中dispatch table就是一個(gè)函數(shù)分發(fā)列表,整個(gè)過(guò)程如下圖:


messaging.png

第二段,主要講的為了加速函數(shù)訪問(wèn),類里面的函數(shù)一旦被調(diào)過(guò)之后會(huì)存進(jìn)緩存里面,這樣,訪問(wèn)函數(shù)先訪問(wèn)緩存,就可以提高訪問(wèn)速度。

3.消息的轉(zhuǎn)發(fā)機(jī)制

在第二部的消息傳遞如果沒(méi)有找到合適的對(duì)象處理當(dāng)前的消息,則開(kāi)始消息轉(zhuǎn)發(fā)流程,如下:

消息轉(zhuǎn)發(fā).png

消息動(dòng)態(tài)處理(resolveInstanceMethod)

void dynamicMethod(id self, SEL aSelector, id argument) {
    NSLog(@"%@", argument);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test:)) {
        class_addMethod([self class], sel, (IMP)dynamicMethod, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod: sel];
}
//參數(shù):v -> void @ -> id  : -> selector
//也就是說(shuō)dynamicMethod返回值為空,參數(shù)為對(duì)象的函數(shù)
//更多的資料請(qǐng)查詢蘋(píng)果官方文檔《Type Encodings》

假如消息動(dòng)態(tài)處理返回NO,則進(jìn)入消息轉(zhuǎn)發(fā)流程,響應(yīng)目標(biāo)轉(zhuǎn)移(forwardingTargetForSelector)

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([ProxyObject instanceMethodForSelector: aSelector]) {
        return [ProxyObject new];
    }
    return [super forwardingTargetForSelector: aSelector];
}
//當(dāng)前對(duì)象不能響應(yīng)轉(zhuǎn)發(fā)的消息,系統(tǒng)會(huì)尋找代替的對(duì)象響應(yīng)改消息

假如消息響應(yīng)目標(biāo)返回nil,則系統(tǒng)會(huì)生成函數(shù)的簽名,進(jìn)行最后的處理(forwardInvocation)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([ProxyObject instancesRespondToSelector: aSelector]) {
        return [ProxyObject instanceMethodSignatureForSelector: aSelector];
    }
    return [super methodSignatureForSelector: aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([ProxyObject instancesRespondToSelector: anInvocation.selector]) {
        [anInvocation invokeWithTarget: [ProxyObject new]];
    } else {
        [super forwardInvocation: anInvocation];
    }
}
//方法簽名為nil,則不會(huì)調(diào)forwardInvocation函數(shù),直接調(diào)doesNotRecognizeSelector函數(shù),報(bào)異常
//方法簽名不為nil,則會(huì)調(diào)用forwardInvocation函數(shù),進(jìn)行最后的處理

直到最后消息都沒(méi)有被處理,則調(diào)用doesNotRecognizeSelector函數(shù),報(bào)異常

- (void)doesNotRecognizeSelector:(SEL)aSelector {
  ...
}

Tips:
1.消息轉(zhuǎn)發(fā)的方法,默認(rèn)會(huì)調(diào)父類響應(yīng)的方法。
2.函數(shù)前面如果返回nil,則不會(huì)調(diào)用forwardInvocation方法,直接報(bào)異常。
3. resolveInstanceMethod方法,會(huì)在組裝函數(shù)簽名時(shí)重新被調(diào)用;如果函數(shù)簽名返回nil,則不會(huì)被調(diào)用。

總結(jié)

這里都是一些關(guān)于runtime消息傳遞的比較淺顯的解釋,如果感興趣想要更深入研究,推薦一篇文章《神經(jīng)病院Objective-C Runtime住院第二天——消息發(fā)送與轉(zhuǎn)發(fā)》里面講的非常的詳細(xì),深入,不過(guò),看懂的話有點(diǎn)難度,需要懂一點(diǎn)點(diǎn)匯編。

參考:

Objective-C Runtime Programming Guide
iOS runtime 之消息轉(zhuǎn)發(fā)
Objective-C Runtime 運(yùn)行時(shí)之三:方法與消息
iOS開(kāi)發(fā)之Runtime運(yùn)行時(shí)機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,032評(píng)論 0 9
  • 2.Runtime儲(chǔ)備知識(shí) 2.1.前言 在體驗(yàn)中,我寫(xiě)過(guò)這樣一句話“并且我認(rèn)為這個(gè)技術(shù)算是高階開(kāi)發(fā)里面一個(gè)...
    2897275c8a00閱讀 656評(píng)論 1 2
  • 我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,319評(píng)論 0 7
  • 刮臺(tái)風(fēng),但在辦公室什么都不知道。 沒(méi)有什么是過(guò)不去的,咬牙切齒的走下去吧。
    理想是不要加班閱讀 251評(píng)論 0 0
  • 你遠(yuǎn)遠(yuǎn)地看著 看他人哭笑 看悲歡離合 黑暗的窺視 隱藏你的顏色 你知道你一直 都是那個(gè)卑微的旁觀者 你在別人的故事...
    一玖酒八閱讀 413評(píng)論 0 7

友情鏈接更多精彩內(nèi)容