當(dāng)我們用中括號(hào)[]
調(diào)用OC函數(shù)的時(shí)候,實(shí)際上會(huì)進(jìn)入消息發(fā)送和消息轉(zhuǎn)發(fā)流程:
消息發(fā)送(Messaging)年局,runtime系統(tǒng)會(huì)根據(jù)
SEL
查找對(duì)用的IMP
级及,查找到拼卵,則調(diào)用函數(shù)指針進(jìn)行方法調(diào)用蓖议;若查找不到,則進(jìn)入動(dòng)態(tài)消息解析和轉(zhuǎn)發(fā)流程讥蟆,如果動(dòng)態(tài)解析和消息轉(zhuǎn)發(fā)失敗勒虾,則程序crash并記錄日志。
在進(jìn)入正題之前瘸彤,我們先思考兩個(gè)問(wèn)題:
類(lèi)實(shí)例可以調(diào)用類(lèi)方法嗎修然? 類(lèi)可以調(diào)用實(shí)例方法嗎? 為什么质况?
下面代碼輸出什么愕宋?
@interface Father : NSObject
@end
@implementation Father
@end
@interface Son : Father
- (void)showClass;
@end
@implementation Son
- (void)showClass {
NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}
...
Son *son = [Son new];
[son showClass]; // 這里輸出什么?
...
消息相關(guān)數(shù)據(jù)結(jié)構(gòu)
SEL
SEL
被稱(chēng)之為消息選擇器结榄,它相當(dāng)于一個(gè)key中贝,在類(lèi)的消息列表中,可以根據(jù)這個(gè)key臼朗,來(lái)查找到對(duì)應(yīng)的消息實(shí)現(xiàn)邻寿。
在runtime中,SEL的定義是這樣的:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
它是一個(gè)不透明的定義视哑,似乎蘋(píng)果故意隱藏了它的實(shí)現(xiàn)绣否。目前SEL僅是一個(gè)字符串。
<font color=red>這里要注意黎炉,即使消息的參數(shù)不同或方法所屬的類(lèi)也不同枝秤,但只要方法名相同,SEL
也是一樣的慷嗜。所以淀弹,SEL
單獨(dú)并不能作為唯一的Key,必須結(jié)合消息發(fā)送的目標(biāo)Class庆械,才能找到最終的IMP
薇溃。</font>
我們可以通過(guò)OC編譯器命令@selector()
或runtime函數(shù)sel_registerName
,來(lái)獲取一個(gè)SEL
類(lèi)型的方法選擇器缭乘。
method_t
當(dāng)需要發(fā)送消息的時(shí)候沐序,runtime會(huì)在Class的方法列表中尋找方法的實(shí)現(xiàn)。在方法列表中方法是以結(jié)構(gòu)體method_t
存儲(chǔ)的堕绩。
struct method_t {
SEL name;
const char *types;
IMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
可以看到method_t
包含一個(gè)SEL
作為key策幼,同時(shí)有一個(gè)指向函數(shù)實(shí)現(xiàn)的指針IMP
。method_t
還包含一個(gè)屬性const char *types;
types是一個(gè)C字符串奴紧,用于表明方法的返回值和參數(shù)類(lèi)型特姐。一般是這種格式的:
v24@0:8@16
關(guān)于SEL type,可以參考Type Encodings
IMP
IMP實(shí)際是一個(gè)函數(shù)指針黍氮,用于實(shí)際的方法調(diào)用唐含。在runtime中定義是這樣的:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP
是由編譯器生成的浅浮,如果我們知道了IMP
的地址,則可以繞過(guò)runtime消息發(fā)送的過(guò)程捷枯,直接調(diào)用函數(shù)實(shí)現(xiàn)滚秩。關(guān)于這一點(diǎn),我們稍后會(huì)談到淮捆。
在消息發(fā)送的過(guò)程中郁油,runtime就是根據(jù)id
和SEL
來(lái)唯一確定IMP
并調(diào)用之的。
消息
當(dāng)我們用[]
向OC對(duì)象發(fā)送消息時(shí)攀痊,編譯器會(huì)對(duì)應(yīng)的代碼修改為objc_msgSend
, 其定義如下:
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
其實(shí)已艰,除了objc_msgSend
,編譯器還會(huì)根據(jù)實(shí)際情況蚕苇,將消息發(fā)送改寫(xiě)為下面四個(gè)msgSend之一:
<font color=red>objc_msgSend
objc_msgSend_stret</font>
<font color=blue>objc_msgSendSuper
objc_msgSendSuper_stret</font>
當(dāng)我們將消息發(fā)送給super class的時(shí)候哩掺,編譯器會(huì)將消息發(fā)送改寫(xiě)為**SendSuper
的格式,如調(diào)用[super viewDidLoad]
涩笤,會(huì)被編譯器改寫(xiě)為objc_msgSendSuper
的形式嚼吞。
objc_msgSendSuper
的定義如下:
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可以看到,調(diào)用super方法時(shí)蹬碧,msgSendSuper
的第一個(gè)參數(shù)不是id self
舱禽,而是一個(gè)objc_super *
。objc_super
定義如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
};
objc_super
包含兩個(gè)數(shù)據(jù)恩沽,receiver
指調(diào)用super方法的對(duì)象誊稚,即子類(lèi)對(duì)象,而super_class
表示子類(lèi)的Super Class罗心。
這就說(shuō)明了在消息過(guò)程中調(diào)用了 super方法和沒(méi)有調(diào)用super方法里伯,還是略有差異的。我們將會(huì)在下面講解渤闷。
至于**msgSend
中以_stret
結(jié)尾的疾瓮,表明方法返回值是一個(gè)結(jié)構(gòu)體類(lèi)型。
在objc_msgSend
的內(nèi)部飒箭,會(huì)依次執(zhí)行:
- 檢測(cè)selector是否是應(yīng)該忽略的狼电,比如在Mac OS X開(kāi)發(fā)中,有了垃圾回收機(jī)制弦蹂,就不會(huì)響應(yīng)
retain
肩碟,release
這些函數(shù)。 - 判斷當(dāng)前
receiver
是否為nil
凸椿,若為nil
削祈,則不做任何響應(yīng),即向nil發(fā)送消息削饵,系統(tǒng)不會(huì)crash岩瘦。 - 檢查
Class
的method cache,若cache未命中窿撬,則進(jìn)而查找Class
的method list
启昧。 - 若在
Class
的method list
中未找到對(duì)應(yīng)的IMP
,則進(jìn)行消息轉(zhuǎn)發(fā) - 若消息轉(zhuǎn)發(fā)失敗劈伴,程序crash
objc_msgSend
objc_msgSend
的偽代碼實(shí)現(xiàn)如下:
id objc_msgSend(id self, SEL cmd, ...) {
if(self == nil)
return 0;
Class cls = objc_getClass(self);
IMP imp = class_getMethodImplementation(cls, cmd);
return imp?imp(self, cmd, ...):0;
}
而在runtime源碼中密末,objc_msgSend
方法其實(shí)是用匯編寫(xiě)的。為什么用匯編跛璧?一是因?yàn)?code>objc_msgSend的返回值類(lèi)型是可變的严里,需要用到匯編的特性;二是因?yàn)閰R編可以提高代碼的效率追城。
對(duì)應(yīng)arm64刹碾,其匯編源碼是這樣的(有所刪減):
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
b.eq LReturnZero // nil check
END_ENTRY _objc_msgSend
雖然不懂匯編,但是結(jié)合注釋?zhuān)€是能夠猜大體意思的座柱。
首先迷帜,系統(tǒng)通過(guò)cmp x0, #0
檢測(cè)receiver
是否為nil
。如果為nil
色洞,則進(jìn)入LNilOrTagged
戏锹,返回0;
如果不為nil
火诸,則現(xiàn)將receiver
的isa
存入x13
寄存器锦针;
在x13
寄存器中,取出isa
中的class
置蜀,放到x16
寄存器中奈搜;
調(diào)用CacheLookup NORMAL
,在這個(gè)函數(shù)中盯荤,首先查找class
的cache媚污,如果未命中,則進(jìn)入objc_msgSend_uncached
廷雅。
objc_msgSend_uncached
也是匯編耗美,實(shí)現(xiàn)如下:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
其內(nèi)部調(diào)用了MethodTableLookup
, MethodTableLookup
是一個(gè)匯編的宏定義航缀,其內(nèi)部會(huì)調(diào)用C語(yǔ)言函數(shù)_class_lookupMethodAndLoadCache3
:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
最終商架,會(huì)調(diào)用到lookUpImpOrForward
來(lái)尋找class
的IMP
實(shí)現(xiàn)或進(jìn)行消息轉(zhuǎn)發(fā)。
lookUpImpOrForward
lookUpImpOrForward
方法的目的在于根據(jù)class
和SEL
芥玉,在class
或其super class
中找到并返回對(duì)應(yīng)的實(shí)現(xiàn)IMP
蛇摸,同時(shí),cache所找到的IMP
到當(dāng)前class
中灿巧。如果沒(méi)有找到對(duì)應(yīng)IMP
赶袄,lookUpImpOrForward
會(huì)進(jìn)入消息轉(zhuǎn)發(fā)流程揽涮。
lookUpImpOrForward
的簡(jiǎn)化版實(shí)現(xiàn)如下:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 先在class的cache中查找imp
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();
// 如果class沒(méi)有被relize,先relize
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
// 如果class沒(méi)有init饿肺,則先init
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
retry:
runtimeLock.assertReading();
// relaized并init了class蒋困,再試一把cache中是否有imp
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 先在當(dāng)前class的method list中查找有無(wú)imp
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 在當(dāng)前class中沒(méi)有找到imp,則依次向上查找super class的方法列表
{
unsigned attempts = unreasonableClassCount();
// 進(jìn)入for循環(huán)敬辣,沿著繼承鏈雪标,依次向上查找super class的方法列表
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 先找super class的cache
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在super class 的cache中找到imp,將imp存儲(chǔ)到當(dāng)前class(注意溉跃,不是super class)的cache中
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// 在Super class的cache中沒(méi)有找到村刨,調(diào)用getMethodNoSuper_nolock在super class的方法列表中查找對(duì)應(yīng)的實(shí)現(xiàn)
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 在class和其所有的super class中均未找到imp,進(jìn)入動(dòng)態(tài)方法解析流程resolveMethod
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// 如果在class撰茎,super classes和動(dòng)態(tài)方法解析 都不能找到這個(gè)imp嵌牺,則進(jìn)入消息轉(zhuǎn)發(fā)流程,嘗試讓別的class來(lái)響應(yīng)這個(gè)SEL
// 消息轉(zhuǎn)發(fā)結(jié)束龄糊,cache結(jié)果到當(dāng)前class
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
通過(guò)上的源碼髓梅,我們可以很清晰的知曉runtime的消息處理流程:
- 嘗試在當(dāng)前receiver對(duì)應(yīng)的class的cache中查找imp
- 嘗試在class的方法列表中查找imp
- 嘗試在class的所有super classes中查找imp(先看Super class的cache,再看super class的方法列表)
- 上面3步都沒(méi)有找到對(duì)應(yīng)的imp绎签,則嘗試動(dòng)態(tài)解析這個(gè)SEL
- 動(dòng)態(tài)解析失敗枯饿,嘗試進(jìn)行消息轉(zhuǎn)發(fā),讓別的class處理這個(gè)SEL
在查找class的方法列表中是否有SEL的對(duì)應(yīng)實(shí)現(xiàn)時(shí)诡必,是調(diào)用函數(shù)getMethodNoSuper_nolock
:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
方法實(shí)現(xiàn)很簡(jiǎn)單奢方,就是在class
的方法列表methods
中,根據(jù)SEL
查找對(duì)應(yīng)的imp
爸舒。
PS:這里順便說(shuō)一下Category
覆蓋類(lèi)原始方法的問(wèn)題蟋字,由于在methods
中是線(xiàn)性查找的,會(huì)返回第一個(gè)和SEL
匹配的imp
扭勉。而在class
的realizeClass
方法中,會(huì)調(diào)用methodizeClass
來(lái)初始化class
的方法列表涂炎。在methodizeClass
方法中忠聚,會(huì)將Category
方法和class
方法合并到一個(gè)列表,同時(shí)唱捣,會(huì)確保Category
方法位于class
方法前面两蟀,這樣,在runtime尋找SEL
的對(duì)應(yīng)實(shí)現(xiàn)時(shí)震缭,會(huì)先找到Category
中定義的imp
返回赂毯,從而實(shí)現(xiàn)了原始方法覆蓋的效果。 關(guān)于Category的底層實(shí)現(xiàn),我們會(huì)Objective-C runtime機(jī)制(4)——深入理解Category中講解党涕。
關(guān)于消息的查找烦感,可以用下圖更清晰的解釋?zhuān)?/p>
runtime用isa找到receiver對(duì)應(yīng)的class,用superClass找到class的父類(lèi)膛堤。
這里用<font color=#3299cc>藍(lán)色的表示實(shí)例方法的消息查找流程:通過(guò)類(lèi)對(duì)象實(shí)例的isa查找到對(duì)象的class手趣,進(jìn)行查找。</font>
用<font color=#ff00ff>紫色表示類(lèi)方法的消息查找流程: 通過(guò)類(lèi)的isa找到類(lèi)對(duì)應(yīng)的<font color=#a67d3d>元類(lèi)</font>, 沿著元類(lèi)的super class鏈一路查找</font>
關(guān)于<font color=#a67d3d>元類(lèi)</font>骑祟,我們?cè)谏弦徽轮幸呀?jīng)提及,元類(lèi)是“類(lèi)的類(lèi)”气笙。因?yàn)樵趓untime中次企,類(lèi)也被看做是一種對(duì)象,而對(duì)象就一定有其所屬的類(lèi)潜圃,因此缸棵,類(lèi)所屬的類(lèi),被稱(chēng)為類(lèi)的<font color=#a67d3d>元類(lèi)(meta class)</font>谭期。
<font color=red>我們所定義的類(lèi)方法堵第,其實(shí)是存儲(chǔ)在元類(lèi)的方法列表中的。</font>
關(guān)于元類(lèi)的更多描述隧出,可以查看這里踏志。
類(lèi)調(diào)用實(shí)例方法
這里有一個(gè)很好玩的地方,注意到在SEL查找鏈的最上方:Root Class
和Root meta Class
胀瞪。
我們上面說(shuō)到针余,對(duì)于類(lèi)方法,是沿著紫色的路線(xiàn)依次查找Super類(lèi)方法列表凄诞。一路上各個(gè)節(jié)點(diǎn)圆雁,都是<font color=#a67d3d>元類(lèi)(meta class)</font>。而注意到帆谍,Root meta Class
的super class竟然是Root class
! 也就是說(shuō)伪朽,當(dāng)在Root meta Class
中找不到類(lèi)方法時(shí),會(huì)轉(zhuǎn)而到Root class
中查找類(lèi)方法汛蝙。而在Root class
中存儲(chǔ)的烈涮,其實(shí)都是<font color=blue>實(shí)例方法</font>。
換句話(huà)說(shuō)窖剑,我們?cè)谕ㄟ^(guò)類(lèi)方法的形式調(diào)用Root class中的實(shí)例方法跃脊,在OC中, 也是可以被解析的苛吱!
比如酪术,在NSObject
中,有一個(gè)實(shí)例方法:
@interface NSObject <NSObject> {
...
- (IMP)methodForSelector:(SEL)aSelector;
...
}
然后,我們自定義類(lèi):
@interface MyObj : NSObject
- (void)showY;
@end
@implementation MyObj
- (void)showY {
NSLog(@"AB");
}
@end
我們分別通過(guò)類(lèi)方法的形式調(diào)用methodForSelector
和 showY
:
[MyObj methodForSelector:@selector(test)];
[MyObj showY];
會(huì)發(fā)現(xiàn)绘雁,編譯器允許methodForSelector
的調(diào)用橡疼,并能夠正常允許。
而對(duì)于showY
庐舟,則會(huì)編譯錯(cuò)誤欣除。
至于原因,就是因?yàn)閷?duì)于Root meta class
挪略,其實(shí)會(huì)到Root class
中尋找對(duì)應(yīng)的SEL實(shí)現(xiàn)历帚。
類(lèi)似的,還有一個(gè)好玩的例子杠娱,在子類(lèi)中挽牢,我們重寫(xiě)NSObject
的respondsToSelector
方法,然后通過(guò)類(lèi)方法和實(shí)例方法兩種形式來(lái)調(diào)用摊求,看看分別會(huì)發(fā)生什么情況:
@interface NSObject <NSObject> {
...
- (BOOL)respondsToSelector:(SEL)aSelector;
...
}
@interface MyObj : NSObject
@end
@implementation MyObj
- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(@"It is my overwrite");
return YES;
}
@end
然后通過(guò)類(lèi)方法和實(shí)例方法分別調(diào)用
[MyObj methodForSelector:@selector(test)];
MyObj *obj = [[MyObj alloc] init];
[obj showY];
這兩種調(diào)用方式會(huì)有什么不同禽拔?
這當(dāng)做一個(gè)思考題,如果大家理解了上面IMP的查找流程室叉,那么應(yīng)該能夠知道答案睹栖。
objc_msgSendSuper
看完了objc_msgSend
方法的調(diào)用流程,我們?cè)賮?lái)看一下objc_msgSendSuper
是如何調(diào)用的茧痕。
當(dāng)我們?cè)诖a里面顯示的調(diào)用super 方法時(shí)野来,runtime就會(huì)調(diào)用objc_msgSendSuper
來(lái)完成消息發(fā)送。
objc_msgSendSuper
的定義如下:
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
當(dāng)我使用super
關(guān)鍵字時(shí)踪旷,在這里梁只,super并不代表某個(gè)確定的對(duì)象,而是編譯器的一個(gè)符號(hào)埃脏,編譯器會(huì)將super替換為objc_super *類(lèi)型
來(lái)傳入objc_msgSendSuper
方法中搪锣。
而objc_super
結(jié)構(gòu)體定義如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
};
第一個(gè)成員id receiver,表明要將消息發(fā)送給誰(shuí)彩掐。它應(yīng)該是我們的類(lèi)實(shí)例(注意构舟,是當(dāng)前類(lèi)實(shí)例,而不是super)
第二個(gè)成員Class super_class堵幽,表明要到哪里去尋找SEL所對(duì)應(yīng)的IMP狗超。它應(yīng)該是我們類(lèi)實(shí)例所對(duì)應(yīng)類(lèi)的super class。(即要直接到super class中尋找IMP朴下,而略過(guò)當(dāng)前class的method list)
簡(jiǎn)單來(lái)說(shuō)努咐,當(dāng)調(diào)用super method時(shí),runtime會(huì)到super class中找到IMP殴胧,然后發(fā)送到當(dāng)前class的實(shí)例上渗稍。因此佩迟,雖然IMP的實(shí)現(xiàn)是用的super class,但是竿屹,最終作用對(duì)象报强,仍然是當(dāng)前class 的實(shí)例。這也就是為什么
NSLog(@"%@ %@",[self class], [super class]);
會(huì)輸出同樣的內(nèi)容拱燃,即[self class]
的內(nèi)容秉溉。
我們來(lái)看一下objc_msgSendSuper
的匯編實(shí)現(xiàn):
ENTRY _objc_msgSendSuper
MESSENGER_START
ldr r9, [r0, #CLASS] // r9 = struct super->class
CacheLookup NORMAL
// cache hit, IMP in r12, eq already set for nonstret forwarding
ldr r0, [r0, #RECEIVER] // load real receiver
MESSENGER_END_FAST
bx r12 // call imp
CacheLookup2 NORMAL
// cache miss
ldr r9, [r0, #CLASS] // r9 = struct super->class
ldr r0, [r0, #RECEIVER] // load real receiver
MESSENGER_END_SLOW
b __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
可以看到,它就是在struct super->class的method list 中尋找對(duì)應(yīng)的IMP碗誉,而real receiver
則是super->receiver
召嘶,即當(dāng)前類(lèi)實(shí)例。
如果在super class的cache中沒(méi)有找到IMP的話(huà)哮缺,則同樣會(huì)調(diào)用__objc_msgSend_uncached
弄跌,這和objc_msgSend
是一樣的,最終都會(huì)調(diào)用到
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
只不過(guò)蝴蜓,這里傳入lookUpImpOrForward
里面的cls碟绑,使用了super class
而已俺猿。
動(dòng)態(tài)解析
如果在類(lèi)的繼承體系中茎匠,沒(méi)有找到相應(yīng)的IMP,runtime首先會(huì)進(jìn)行消息的動(dòng)態(tài)解析押袍。<font color=red>所謂動(dòng)態(tài)解析诵冒,就是給我們一個(gè)機(jī)會(huì),將方法實(shí)現(xiàn)在運(yùn)行時(shí)動(dòng)態(tài)的添加到當(dāng)前的類(lèi)中谊惭。</font>然后汽馋,runtime會(huì)重新嘗試走一遍消息查找的過(guò)程:
// 在class和其所有的super class中均未找到imp,進(jìn)入動(dòng)態(tài)方法解析流程resolveMethod
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
在源碼中圈盔,可以看到豹芯,runtime會(huì)調(diào)用_class_resolveMethod
,讓用戶(hù)進(jìn)行動(dòng)態(tài)方法解析驱敲,而且設(shè)置標(biāo)記triedResolver = YES
铁蹈,僅執(zhí)行一次。當(dāng)動(dòng)態(tài)解析完畢众眨,<font color=blue>不管用戶(hù)是否作出了相應(yīng)處理</font>握牧,runtime,都會(huì)goto retry
娩梨, 重新嘗試查找一遍類(lèi)的消息列表沿腰。
根據(jù)是調(diào)用的實(shí)例方法或類(lèi)方法,runtime會(huì)在對(duì)應(yīng)的類(lèi)中調(diào)用如下方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel // 動(dòng)態(tài)解析實(shí)例方法
+ (BOOL)resolveClassMethod:(SEL)sel // 動(dòng)態(tài)解析類(lèi)方法
resolveInstanceMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel
用來(lái)動(dòng)態(tài)解析實(shí)例方法狈定,我們需要在運(yùn)行時(shí)動(dòng)態(tài)的將對(duì)應(yīng)的方法實(shí)現(xiàn)添加到類(lèi)實(shí)例所對(duì)應(yīng)的類(lèi)的消息列表中:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(singSong)) {
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(unrecoginzedInstanceSelector)), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)unrecoginzedInstanceSelector {
NSLog(@"It is a unrecoginzed instance selector");
}
resolveClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel
用于動(dòng)態(tài)解析類(lèi)方法颂龙。 我們同樣需要將類(lèi)的實(shí)現(xiàn)動(dòng)態(tài)的添加到相應(yīng)類(lèi)的消息列表中。
<font color=blue>但這里需要注意,調(diào)用類(lèi)方法的‘對(duì)象’實(shí)際也是一個(gè)類(lèi)厘托,而類(lèi)所對(duì)應(yīng)的類(lèi)應(yīng)該是元類(lèi)
友雳。要添加類(lèi)方法,我們必須把方法的實(shí)現(xiàn)添加到元類(lèi)
的方法列表中铅匹。</font>
在這里押赊,我們就不能夠使用[self class]
了,它僅能夠返回當(dāng)前的類(lèi)包斑。而是需要使用object_getClass(self)
流礁,它其實(shí)會(huì)返回isa所指向的類(lèi),即類(lèi)所對(duì)應(yīng)的元類(lèi)
(注意罗丰,因?yàn)楝F(xiàn)在是在類(lèi)方法里面神帅,self所指的是Class,而通過(guò)object_getClass(self)
獲取self的類(lèi)萌抵,自然是元類(lèi)
)找御。
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(payMoney)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(unrecognizedClassSelector)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (void)unrecognizedClassSelector {
NSLog(@"It is a unrecoginzed class selector");
}
這里主要弄清楚,類(lèi)
绍填,元類(lèi)
霎桅,實(shí)例方法
和類(lèi)方法
在不同地方存儲(chǔ),就清楚了讨永。
關(guān)于class
方法和object_getClass
方法的區(qū)別:
當(dāng)self
是實(shí)例對(duì)象時(shí)滔驶,[self class]
與object_getClass(self)
等價(jià),因?yàn)榍罢邥?huì)調(diào)用后者卿闹,都會(huì)返回對(duì)象實(shí)例所對(duì)應(yīng)的類(lèi)揭糕。
當(dāng)self
是類(lèi)對(duì)象時(shí),[self class]
返回類(lèi)對(duì)象自身锻霎,而object_getClass(self)
返回類(lèi)所對(duì)應(yīng)的元類(lèi)
著角。
消息轉(zhuǎn)發(fā)
當(dāng)動(dòng)態(tài)解析失敗,則進(jìn)入消息轉(zhuǎn)發(fā)流程旋恼。所謂消息轉(zhuǎn)發(fā)吏口,是將當(dāng)前消息轉(zhuǎn)發(fā)到其它對(duì)象進(jìn)行處理。
- (id)forwardingTargetForSelector:(SEL)aSelector // 轉(zhuǎn)發(fā)實(shí)例方法
+ (id)forwardingTargetForSelector:(SEL)aSelector // 轉(zhuǎn)發(fā)類(lèi)方法蚌铜,id需要返回類(lèi)對(duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(xxx)) {
return NSClassFromString(@"Class name");
}
return [super forwardingTargetForSelector:aSelector];
}
如果forwardingTargetForSelector
沒(méi)有實(shí)現(xiàn)拘荡,或返回了nil
或self
漾根,則會(huì)進(jìn)入另一個(gè)轉(zhuǎn)發(fā)流程递胧。
它會(huì)依次調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
募寨,然后runtime會(huì)根據(jù)該方法返回的值,組成一個(gè)NSInvocation
對(duì)象审葬,并調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation
深滚。<font color='red'>注意奕谭,當(dāng)調(diào)用到forwardInvocation時(shí),無(wú)論我們是否實(shí)現(xiàn)了該方法痴荐,系統(tǒng)都默認(rèn)消息已經(jīng)得到解析血柳,不會(huì)引起crash。</font>
整個(gè)消息轉(zhuǎn)發(fā)流程可以用下圖表示:
<font color=orange>注意生兆,和動(dòng)態(tài)解析不同难捌,由于消息轉(zhuǎn)發(fā)實(shí)際上是將消息轉(zhuǎn)發(fā)給另一種對(duì)象處理。而動(dòng)態(tài)解析仍是嘗試在當(dāng)前類(lèi)范圍內(nèi)進(jìn)行處理鸦难。</font>
消息轉(zhuǎn)發(fā) & 多繼承
通過(guò)消息轉(zhuǎn)發(fā)流程根吁,我們可以模擬實(shí)現(xiàn)OC的多繼承機(jī)制。詳情可以參考官方文檔合蔽。
直接調(diào)用IMP
runtime的消息解析击敌,究其根本,實(shí)際上就是根據(jù)SEL查找到對(duì)應(yīng)的IMP拴事,并調(diào)用之沃斤。如果我們可以直接知道IMP的所在,就不用再走消息機(jī)制這一層了刃宵。似乎不走消息機(jī)制會(huì)提高一些方法調(diào)用的速度衡瓶,但現(xiàn)實(shí)是這樣的嗎?
我們比較一下:
CGFloat BNRTimeBlock (void (^block)(void)) {
mach_timebase_info_data_t info;
if (mach_timebase_info(&info) != KERN_SUCCESS) return -1.0;
uint64_t start = mach_absolute_time ();
block ();
uint64_t end = mach_absolute_time ();
uint64_t elapsed = end - start;
uint64_t nanos = elapsed * info.numer / info.denom;
return (CGFloat)nanos / NSEC_PER_SEC;
} // BNRTimeBlock
Son *mySon1 = [Son new];
setter ss = (void (*)(id, SEL, BOOL))[mySon1 methodForSelector:@selector(setFilled:)];
CGFloat timeCost1 = BNRTimeBlock(^{
for (int i = 0; i < 1000; ++i) {
ss(mySon1, @selector(setFilled:), YES);
}
});
CGFloat timeCost2 = BNRTimeBlock(^{
for (int i = 0; i < 1000; ++i) {
[mySon1 setFilled:YES];
}
});
將timeCost1和timeCost2打印出來(lái)组去,你會(huì)發(fā)現(xiàn)鞍陨,僅僅相差0.000001秒步淹,幾乎可以忽略不計(jì)从隆。這樣是因?yàn)樵谙C(jī)制中,有緩存的存在缭裆。
總結(jié)
在本文中键闺,我們了解了OC語(yǔ)言中方法調(diào)用實(shí)現(xiàn)的底層機(jī)制——消息機(jī)制。并了解了self method和super method的異同澈驼。
最后辛燥,讓我們回答文章開(kāi)頭的兩個(gè)問(wèn)題:
- 類(lèi)實(shí)例可以調(diào)用類(lèi)方法嗎? 類(lèi)可以調(diào)用實(shí)例方法嗎缝其? 為什么挎塌?
類(lèi)實(shí)例不可用調(diào)用類(lèi)方法,因?yàn)轭?lèi)實(shí)例查找消息IMP的流程僅會(huì)沿著繼承鏈查找class 的method list内边,而對(duì)于類(lèi)方法來(lái)說(shuō)榴都,是存于meta class的method list 的,因此類(lèi)實(shí)例通過(guò)objc_msgSend方法是找不到對(duì)應(yīng)的實(shí)現(xiàn)的漠其。
類(lèi)大多數(shù)情況下是不能夠調(diào)用實(shí)例方法的嘴高,除非實(shí)例方法定義在root class——NSObject中竿音。因?yàn)椋?dāng)調(diào)用類(lèi)方法時(shí)拴驮,會(huì)在meta class的繼承鏈的method list 查找對(duì)應(yīng)的IMP春瞬,而root meta class對(duì)應(yīng)的super class是NSObject,因此在NSObject中定義的實(shí)例方法套啤,其實(shí)是可以通過(guò)類(lèi)方法形式來(lái)調(diào)用的宽气。
- 下面代碼輸出什么?
@interface Father : NSObject
@end
@implementation Father
@end
@interface Son : Father
- (void)showClass;
@end
@implementation Son
- (void)showClass {
NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}
...
Son *son = [Son new];
[son showClass]; // 這里輸出什么潜沦?
...
會(huì)輸出
self class = Son, super class = Son
至于原因抹竹,可以到本文關(guān)于objc_msgSendSuper
相關(guān)講解中查看。
參考文獻(xiàn)
Objective-C Runtime
Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理
object_getClass與objc_getClass的不同