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í)搅窿,其生成的選擇子是完全相同的。
- Objective-C 為我們維護(hù)了一個(gè)巨大的選擇子表
- 在使用 @selector()時(shí)會(huì)從這個(gè)選擇子表中根據(jù)選擇子的名字查找對(duì)應(yīng)的 SEL隙券。如果沒有找到男应,則會(huì)生成一個(gè) SEL 并添加到表中
- 在編譯期間會(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ì)于方法的查找步驟:
-
NilTest
判斷接受消息的對(duì)象是否為nil,如果為nil久直接返回nil娱仔,這里解釋了為什么向nil對(duì)象發(fā)送消息不會(huì)產(chǎn)生奔潰沐飘,因?yàn)橄⒃谶@里判斷后直接忽略掉 -
CacheLookup
在類的緩存中查找 selector 對(duì)應(yīng)的 IMP(放到 r10)并執(zhí)行。如果緩存沒中牲迫,那就得到 Class 的方法表中查找了耐朴。 -
MethodTableLookup
負(fù)責(zé)在緩存沒命中時(shí)在方法表中負(fù)責(zé)查找 IMP -
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ā)過程
- 先調(diào)用 forwardingTargetForSelector 方法獲取新的 target 作為 receiver 重新執(zhí)行 selector影晓,如果返回的內(nèi)容不合法(為 nil 或者跟舊 receiver 一樣),那就進(jìn)入第二步奶稠。
- 調(diào)用 methodSignatureForSelector 獲取方法簽名后俯艰,判斷返回類型信息是否正確,再調(diào)用 forwardInvocation 執(zhí)行 NSInvocation 對(duì)象锌订,并將結(jié)果返回竹握。如果對(duì)象沒實(shí)現(xiàn) methodSignatureForSelector 方法,進(jìn)入第三步辆飘。
- 調(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ā)送