Objective-C 中的消息與消息轉(zhuǎn)發(fā)

1. 發(fā)送消息

[receiver message];
都會(huì)被轉(zhuǎn)化成
objc_msgSend(receiver, @selector(message));

[object hello] -> objc_msgSend(object, @selector(hello))

我們看一下蘋果對(duì)于objc_msgSend的官方說明
id objc_msgSend(id self, SEL _cmd, ...)
其中self是消息接收對(duì)象宣决,_cmd是需要發(fā)送消息的Selector,...是可變參數(shù)列表

當(dāng)向一般對(duì)象發(fā)送消息時(shí)辟癌,調(diào)用objc_msgSend针饥;當(dāng)向super發(fā)送消息時(shí)坏匪,調(diào)用的是objc_msgSendSuper喳资; 如果返回值是一個(gè)結(jié)構(gòu)體檀头,則會(huì)調(diào)用objc_msgSend_stret或objc_msgSendSuper_stret鲁猩。

使用objc_msgSend的時(shí)候需要對(duì)方法進(jìn)行強(qiáng)制轉(zhuǎn)換
((void (*)(id, SEL))(void *)objc_msgSend)((id)receiver, sel_registerName("message"));
每一個(gè)方法都默認(rèn)有兩個(gè)參數(shù)坎怪,調(diào)用方法的對(duì)象和方法的SEL

1.1 selector

@selector(hello) 生成的選擇子 SEL

結(jié)論:
使用 @selector()生成的選擇子不會(huì)因?yàn)轭惖牟煌淖儯鋬?nèi)存地址在編譯期間就已經(jīng)確定了廓握。也就是說向不同的類發(fā)送相同的消息時(shí)搅窿,其生成的選擇子是完全相同的。

  1. Objective-C 為我們維護(hù)了一個(gè)巨大的選擇子表
  2. 在使用 @selector()時(shí)會(huì)從這個(gè)選擇子表中根據(jù)選擇子的名字查找對(duì)應(yīng)的 SEL隙券。如果沒有找到男应,則會(huì)生成一個(gè) SEL 并添加到表中
  3. 在編譯期間會(huì)掃描全部的頭文件和實(shí)現(xiàn)文件將其中的方法以及使用 @selector() 生成的選擇子加入到選擇子表中

1.2 消息發(fā)送的過程

objc_msgSend的偽代碼

id objc_msgSend(id self, SEL _cmd, ...) {
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

class_getMethodImplementation中對(duì)于方法的查找步驟:

  1. NilTest 判斷接受消息的對(duì)象是否為nil,如果為nil久直接返回nil娱仔,這里解釋了為什么向nil對(duì)象發(fā)送消息不會(huì)產(chǎn)生奔潰沐飘,因?yàn)橄⒃谶@里判斷后直接忽略掉
  2. CacheLookup 在類的緩存中查找 selector 對(duì)應(yīng)的 IMP(放到 r10)并執(zhí)行。如果緩存沒中牲迫,那就得到 Class 的方法表中查找了耐朴。
  3. MethodTableLookup 負(fù)責(zé)在緩存沒命中時(shí)在方法表中負(fù)責(zé)查找 IMP
  4. lookUpImpOrForward 快速查找 IMP,判斷這個(gè)類是否實(shí)現(xiàn)初始化盹憎,如果類沒有初始化筛峭,會(huì)調(diào)用類的initialize對(duì)類進(jìn)行初始化操作,然后在類的methodLists里進(jìn)行查找,如果查找不到就從繼承體系里面查找

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

如果在上面的方法中還是找不到對(duì)應(yīng)的方法陪每,就會(huì)進(jìn)入以下的消息轉(zhuǎn)發(fā)過程

  1. 先調(diào)用 forwardingTargetForSelector 方法獲取新的 target 作為 receiver 重新執(zhí)行 selector影晓,如果返回的內(nèi)容不合法(為 nil 或者跟舊 receiver 一樣),那就進(jìn)入第二步奶稠。
  2. 調(diào)用 methodSignatureForSelector 獲取方法簽名后俯艰,判斷返回類型信息是否正確,再調(diào)用 forwardInvocation 執(zhí)行 NSInvocation 對(duì)象锌订,并將結(jié)果返回竹握。如果對(duì)象沒實(shí)現(xiàn) methodSignatureForSelector 方法,進(jìn)入第三步辆飘。
  3. 調(diào)用 doesNotRecognizeSelector 方法啦辐。

過程說明
在上一步類和父類中找不到對(duì)應(yīng)的方法谓传,會(huì)根據(jù)當(dāng)前的類是不是元類在 _class_resolveInstanceMethod 和 _class_resolveClassMethod 中選擇一個(gè)進(jìn)行調(diào)用

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) {  
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
        // 沒有找到 resolveInstanceMethod: 方法,直接返回芹关。
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // 緩存結(jié)果续挟,以防止下次在調(diào)用 resolveInstanceMethod: 方法影響性能。
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}

在這里如果有實(shí)現(xiàn)resolveInstanceMethod就會(huì)進(jìn)行消息轉(zhuǎn)發(fā)侥衬,如果沒有诗祸,直接奔潰,如果實(shí)現(xiàn)了resolveInstanceMethod會(huì)在之類也進(jìn)行方法緩存轴总,下次調(diào)用該方法直接可以在緩存中查找到

2.1 resolveInstanceMethod

在這里會(huì)根據(jù)方法是對(duì)象方法還是類方法進(jìn)行不同的調(diào)用resolveInstanceMethod或者resolveClassMethod
在這里可以利用runtime的class_addMethod來對(duì)選擇子指定對(duì)應(yīng)的IMP實(shí)現(xiàn)指針直颅,返回YES,這樣就能讓消息得到響應(yīng)

2.2 備用接收者 forwardingTargetForSelector

在這里會(huì)把消息轉(zhuǎn)發(fā)給其他的接收者來處理怀樟,這樣可以做到多繼承的效果

2.3 完整的消息轉(zhuǎn)發(fā)

- forwardInvocation NSInvocation對(duì)象包含了選擇子功偿,目標(biāo)及參數(shù)。只需要在這個(gè)方法中改變方法目標(biāo)往堡,使目標(biāo)在新的目標(biāo)上得以調(diào)用即可

參考資料

Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理
從源代碼看 ObjC 中消息的發(fā)送

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末械荷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子虑灰,更是在濱河造成了極大的恐慌吨瞎,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘩缆,死亡現(xiàn)場(chǎng)離奇詭異关拒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庸娱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門着绊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熟尉,你說我怎么就攤上這事归露。” “怎么了斤儿?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵剧包,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我往果,道長(zhǎng)疆液,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任陕贮,我火速辦了婚禮堕油,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己掉缺,他們只是感情好卜录,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眶明,像睡著了一般艰毒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搜囱,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天丑瞧,我揣著相機(jī)與錄音,去河邊找鬼蜀肘。 笑死嗦篱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的幌缝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼诫欠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼涵卵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荒叼,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤轿偎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后被廓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坏晦,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年嫁乘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昆婿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜓斧,死狀恐怖仓蛆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挎春,我是刑警寧澤看疙,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站直奋,受9級(jí)特大地震影響能庆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脚线,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一搁胆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦丰涉、人聲如沸拓巧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肛度。三九已至,卻和暖如春投慈,著一層夾襖步出監(jiān)牢的瞬間承耿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工伪煤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留加袋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓抱既,卻偏偏與公主長(zhǎng)得像职烧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子防泵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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