【iOS面試糧食】Runtime—消息傳遞和轉(zhuǎn)發(fā)機(jī)制、Method Swizzling

本文章將記錄Objective-C中消息傳遞和轉(zhuǎn)發(fā)機(jī)制济蝉、Method Swizzling的相關(guān)資料,如有錯(cuò)誤歡迎指出~

Objective-C 本質(zhì)上是一種基于 C 語(yǔ)言的領(lǐng)域特定語(yǔ)言澜术。C 語(yǔ)言是一門靜態(tài)語(yǔ)言,其在編譯時(shí)決定調(diào)用哪個(gè)函數(shù)猬腰。而 Objective-C 則是一門動(dòng)態(tài)語(yǔ)言瘪板,其在編譯時(shí)不能決定最終執(zhí)行時(shí)調(diào)用哪個(gè)函數(shù)(Objective-C 中函數(shù)調(diào)用稱為消息傳遞)。Objective-C 的這種動(dòng)態(tài)綁定機(jī)制正是通過(guò) runtime 這樣一個(gè)中間層實(shí)現(xiàn)的漆诽。

消息傳遞(方法調(diào)用)

在 Objective-C 中,消息直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)上锣枝。編譯器會(huì)將消息表達(dá)式轉(zhuǎn)化為一個(gè)消息函數(shù)的調(diào)用厢拭。

OC中的消息表達(dá)式如下(方法調(diào)用)

id returnValue = [someObject messageName:parameter];

編譯器看到這條消息會(huì)轉(zhuǎn)換成一條標(biāo)準(zhǔn)的 C 語(yǔ)言函數(shù)調(diào)用

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

我們可以看到轉(zhuǎn)換中,使用到了objc_msgSend 函數(shù)撇叁,這個(gè)函數(shù)將消息接收者和方法名作為主要參數(shù)供鸠,如下所示:

objc_msgSend(receiver, selector)                    // 不帶參數(shù)
objc_msgSend(receiver, selector, arg1, arg2,...)    // 帶參數(shù)

objc_msgSend 通過(guò)以下幾個(gè)步驟實(shí)現(xiàn)了動(dòng)態(tài)綁定機(jī)制。

  • 首先陨闹,獲取 selector 指向的方法實(shí)現(xiàn)楞捂。由于相同的方法可能在不同的類中有著不同的實(shí)現(xiàn),因此根據(jù) receiver 所屬的類進(jìn)行判斷趋厉。
  • 其次寨闹,傳遞 receiver 對(duì)象、方法指定的參數(shù)來(lái)調(diào)用方法實(shí)現(xiàn)君账。
  • 最后繁堡,返回方法實(shí)現(xiàn)的返回值。

消息傳遞的關(guān)鍵在于【iOS面試糧食】Runtime—實(shí)例對(duì)象乡数、類對(duì)象椭蹄、元類對(duì)象記錄過(guò)的 objc_class 結(jié)構(gòu)體,其有兩個(gè)關(guān)鍵的字段:

  • isa:指向父類的指針
  • methodLists: 類的方法分發(fā)表(dispatch table

當(dāng)創(chuàng)建一個(gè)新對(duì)象時(shí)净赴,先為其分配內(nèi)存绳矩,并初始化其成員變量。其中 isa 指針也會(huì)被初始化玖翅,讓對(duì)象可以訪問(wèn)類及類的繼承鏈翼馆。

下圖所示為消息傳遞過(guò)程的示意圖。

img

  • 當(dāng)消息傳遞給一個(gè)對(duì)象時(shí)烧栋,首先從運(yùn)行時(shí)系統(tǒng)緩存 objc_cache 中進(jìn)行查找写妥。如果找到,則執(zhí)行审姓。否則珍特,繼續(xù)執(zhí)行下面步驟。
  • objc_msgSend 通過(guò)對(duì)象的 isa 指針獲取到類的結(jié)構(gòu)體魔吐,然后在方法分發(fā)表 methodLists 中查找方法的 selector扎筒。如果未找到莱找,將沿著類的 isa 找到其父類,并在父類的分發(fā)表 methodLists 中繼續(xù)查找嗜桌。
  • 以此類推奥溺,一直沿著類的繼承鏈追溯至 NSObject 類。一旦找到 selector骨宠,傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí)現(xiàn)浮定,并將該方法加入緩存 objc_cache 。如果最后仍然沒(méi)有找到 selector层亿,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程

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

當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí)桦卒,會(huì)走正常的消息傳遞流程。當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí)匿又,會(huì)發(fā)生什么呢方灾?

  • 默認(rèn)情況下,如果以 [object message] 的形式調(diào)用方法碌更,如果 object 無(wú)法響應(yīng) message 消息時(shí)裕偿,編譯器會(huì)報(bào)錯(cuò)。
  • 如果是以 performSeletor: 的形式調(diào)用方法痛单,則需要等到運(yùn)行時(shí)才能確定 object 是否能接收 message 消息嘿棘。如果不能,則程序崩潰桦他。

對(duì)于后者蔫巩,當(dāng)不確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí),可以調(diào)用 respondsToSelector: 來(lái)進(jìn)行判斷快压。

if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
}

事實(shí)上圆仔,當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí),就會(huì)啟動(dòng)所謂“消息轉(zhuǎn)發(fā)(message forwarding)”機(jī)制蔫劣。通過(guò)消息轉(zhuǎn)發(fā)機(jī)制坪郭,我們可以告訴對(duì)象如何處理未知的消息。

消息轉(zhuǎn)發(fā)機(jī)制大致可分為三個(gè)步驟:

  • 動(dòng)態(tài)方法解析(Dynamic Method Resolution)
  • 備用接收者
  • 完整消息轉(zhuǎn)發(fā)

下圖所示為消息轉(zhuǎn)發(fā)過(guò)程的示意圖脉幢。

img

動(dòng)態(tài)方法解析

這是整個(gè)消息轉(zhuǎn)發(fā)流程的第一個(gè)階段歪沃,如果在收到無(wú)法響應(yīng)的消息后,會(huì)調(diào)用所屬類的方法:

//實(shí)例對(duì)象
+ (BOOL)resolveInstanceMethod:(SEL)selector
//類對(duì)象
+ (BOOL)resolveClassMethod:(SEL)selector

其中參數(shù)selector為未處理的方法嫌松。

返回值@return表示能否新增一個(gè)方法來(lái)處理沪曙,一般使用@dynamic屬性來(lái)實(shí)現(xiàn):

/************** 使用 resolveInstanceMethod 實(shí)現(xiàn) @dynamic 屬性 **************/
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);
+ (BOOL)resolveInstanceMethod:(SEL)selector
{
    NSString *selectorString = NSStringFromSelector(selector);
    if (/* selector is from a @dynamic property */)
    {
        if ([selectorString hasPrefix:@"set"])
        {
            // 添加 setter 方法
            class_addMethod(self, selector, (IMP)autoDictionarySetter, "v@:@");
        }
        else
        {
            // 添加 getter 方法
            class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
    }
    return [super resolveInstanceMethod:selector];
}

備援接受者

這是整個(gè)消息轉(zhuǎn)發(fā)機(jī)制的第二站,看名字就可以看出來(lái)萎羔,這是在尋找一個(gè)備用援救的接受者液走,到了這一階段,系統(tǒng)會(huì)調(diào)用這個(gè)方法:

- (id)forwardingTargetForSelector:(SEL)aSelector;

傳入?yún)?shù)aSelector同樣為無(wú)法處理的方法。

返回值為當(dāng)前找到的備援接受者缘眶,如果沒(méi)有則返回nil嘱根,進(jìn)入下一階段。

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

如果前兩個(gè)階段都沒(méi)有辦法處理消息巷懈,就會(huì)啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制该抒。

首先會(huì)創(chuàng)建NSInvocation對(duì)象,把尚未處理的那條消息的全部信息細(xì)節(jié)裝在里邊顶燕,在觸發(fā)NSInvocation對(duì)象時(shí)凑保,系統(tǒng)派發(fā)系統(tǒng)(message-dispatch system)將會(huì)把消息指派給目標(biāo)對(duì)象。這時(shí)會(huì)調(diào)用該方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation;

傳入的參數(shù)anInvocation就包含了消息的所有內(nèi)容涌攻。

如果此時(shí)還是沒(méi)辦法處理消息愉适,就會(huì)沿著繼承的順序一步一步向父類調(diào)用相同的方法,直到最后的NSObject類中癣漆,這時(shí)候如果還沒(méi)有辦法處理消息,就會(huì)調(diào)用doesNotRecognizeSelector:拋出異常剂买。

到此為止惠爽,消息轉(zhuǎn)發(fā)的整個(gè)流程就都結(jié)束了。

Method Swizzling

談到黑科技瞬哼,就不得不提一下Objective-C 中的 Method Swizzling 技術(shù)婚肆,它可以允許我們動(dòng)態(tài)地替換方法的實(shí)現(xiàn),實(shí)現(xiàn) Hook 功能坐慰,是一種比子類化更加靈活的“重寫”方法的方式较性。就是說(shuō)在開(kāi)發(fā)中,我們可能會(huì)遇到系統(tǒng)提供的 API 不能滿足實(shí)際需求结胀,我們希望能夠修改它以達(dá)到期望的效果赞咙。

Method Swizzling 原理

Method Swizzling 的實(shí)現(xiàn)充分利用了動(dòng)態(tài)綁定機(jī)制。

在 Objective-C 中調(diào)用方法糟港,其實(shí)是向一個(gè)對(duì)象發(fā)送消息攀操,而查找消息的唯一依據(jù)是方法名 selector。每個(gè)類都有一個(gè)方法列表 objc_method_list秸抚,存放著其所有的方法 objc_method速和。

typedef struct objc_method *Method
struct objc_method{
    SEL method_name      OBJC2_UNAVAILABLE; // 方法名
    char *method_types   OBJC2_UNAVAILABLE;
    IMP method_imp       OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}

每個(gè)方法 objc_method 保存了方法名(SEL)和方法實(shí)現(xiàn)(IMP)的映射關(guān)系。Method Swizzling 其實(shí)就是重置了 SELIMP 的映射關(guān)系剥汤。如下圖所示:

img

具體的應(yīng)用場(chǎng)景可以參考 iOS 開(kāi)發(fā):『Runtime』詳解(二)Method Swizzling

參考

iOS開(kāi)發(fā)-runtime-消息傳遞和轉(zhuǎn)發(fā)機(jī)制

Objective-C Runtime 消息傳遞與轉(zhuǎn)發(fā)

Effective Objective-C Notes:理解消息傳遞機(jī)制

Objective-C 關(guān)聯(lián)對(duì)象與 Method Swizzling

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颠放,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吭敢,更是在濱河造成了極大的恐慌碰凶,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痒留,居然都是意外死亡谴麦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門伸头,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)匾效,“玉大人,你說(shuō)我怎么就攤上這事恤磷∶婧撸” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵扫步,是天一觀的道長(zhǎng)魔策。 經(jīng)常有香客問(wèn)我,道長(zhǎng)河胎,這世上最難降的妖魔是什么闯袒? 我笑而不...
    開(kāi)封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮游岳,結(jié)果婚禮上政敢,老公的妹妹穿的比我還像新娘。我一直安慰自己胚迫,他們只是感情好喷户,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著访锻,像睡著了一般褪尝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上期犬,一...
    開(kāi)封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天河哑,我揣著相機(jī)與錄音,去河邊找鬼龟虎。 笑死灾馒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遣总。 我是一名探鬼主播睬罗,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼旭斥!你這毒婦竟也來(lái)了容达?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤垂券,失蹤者是張志新(化名)和其女友劉穎花盐,沒(méi)想到半個(gè)月后羡滑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡算芯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年柒昏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熙揍。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡职祷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出届囚,到底是詐尸還是另有隱情有梆,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布意系,位于F島的核電站泥耀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛔添。R本人自食惡果不足惜痰催,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迎瞧。 院中可真熱鬧陨囊,春花似錦、人聲如沸夹攒。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咏尝。三九已至,卻和暖如春啸罢,著一層夾襖步出監(jiān)牢的瞬間编检,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工扰才, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留允懂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓衩匣,卻偏偏與公主長(zhǎng)得像蕾总,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琅捏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 766評(píng)論 0 1
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí)生百,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 806評(píng)論 0 4
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中柄延。 以下內(nèi)容是我通過(guò)整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 924評(píng)論 0 6
  • 概述 Objc Runtime使得C具有了面向?qū)ο竽芰︷信#诔绦蜻\(yùn)行時(shí)創(chuàng)建,檢查勇吊,修改類曼追、對(duì)象和它們的方法。Runt...
    星光社的戴銘閱讀 4,022評(píng)論 5 40
  • 如果有一顆后悔藥汉规,請(qǐng)你記得吃… 小時(shí)候礼殊,你很喜歡一件玩具你很喜歡,但是爸爸媽媽會(huì)說(shuō)针史,等過(guò)一會(huì)就會(huì)給你買的晶伦,殊不知...
    L君有話說(shuō)閱讀 246評(píng)論 2 2