當(dāng)我們用中括號[]調(diào)用OC函數(shù)的時候尼夺,實際上會進入消息發(fā)送和消息轉(zhuǎn)發(fā)流程.
消息發(fā)送(Messaging),runtime系統(tǒng)會根據(jù)SEL查找對應(yīng)的IMP炒瘸,查找到淤堵,則調(diào)用函數(shù)指針進行方法調(diào)用;若查找不到顷扩,則進入動態(tài)消息解析和轉(zhuǎn)發(fā)流程拐邪,如果動態(tài)解析和消息轉(zhuǎn)發(fā)失敗,則程序crash并記錄日志隘截。
消息相關(guān)數(shù)據(jù)結(jié)構(gòu)
SEL
SEL被稱之為消息選擇器扎阶,它相當(dāng)于一個key,在類的消息列表中婶芭,可以根據(jù)這個key东臀,來查找到對應(yīng)的消息實現(xiàn)。
在runtime中犀农,SEL的定義是這樣的:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
它是一個不透明的定義惰赋,似乎蘋果故意隱藏了它的實現(xiàn)。目前SEL僅是一個字符串呵哨。
里要注意赁濒,即使消息的參數(shù)類型不同(注意,不是指參數(shù)數(shù)量不同)或方法所屬的類也不同孟害,但只要方法名相同拒炎,SEL也是一樣的。所以挨务,SEL單獨并不能作為唯一的Key击你,必須結(jié)合消息發(fā)送的目標Class玉组,才能找到最終的IMP。
我們可以通過OC編譯器命令@selector()或runtime函數(shù)sel_registerName丁侄,來獲取一個SEL類型的方法選擇器球切。
method_t
當(dāng)需要發(fā)送消息的時候,runtime會在Class的方法列表中尋找方法的實現(xiàn)绒障。在方法列表中方法是以結(jié)構(gòu)體method_t存儲的吨凑。
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包含一個SEL作為key,同時有一個指向函數(shù)實現(xiàn)的指針I(yè)MP户辱。method_t還包含一個屬性const char *types;
types是一個C字符串鸵钝,用于表明方法的返回值和參數(shù)類型。一般是這種格式的:
v24@0:8@16
關(guān)于SEL type庐镐,可以參考 Type Encodings
IMP
IMP實際是一個函數(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的地址怠堪,則可以繞過runtime消息發(fā)送的過程,直接調(diào)用函數(shù)實現(xiàn)名眉。關(guān)于這一點粟矿,我們稍后會談到。
在消息發(fā)送的過程中损拢,runtime就是根據(jù)id和SEL來唯一確定IMP并調(diào)用之的陌粹。
消息
當(dāng)我們用[]向OC對象發(fā)送消息時,編譯器會對應(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);
其實福压,除了objc_msgSend掏秩,編譯器還會根據(jù)實際情況,將消息發(fā)送改寫為下面四個msgSend之一:
objc_msgSend
objc_msgSend_stret
objc_msgSendSuper
objc_msgSendSuper_stret
當(dāng)我們將消息發(fā)送給super class的時候荆姆,編譯器會將消息發(fā)送改寫為**SendSuper的格式蒙幻,如調(diào)用[super viewDidLoad],會被編譯器改寫為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方法時,msgSendSuper的第一個參數(shù)不是id self腐泻,而是一個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包含兩個數(shù)據(jù)队询,receiver指調(diào)用super方法的對象派桩,即子類對象,而super_class表示子類的Super Class蚌斩。
這就說明了在消息過程中調(diào)用了 super方法和沒有調(diào)用super方法铆惑,還是略有差異的。我們將會在下面講解。
至于**msgSend中以_stret結(jié)尾的员魏,表明方法返回值是一個結(jié)構(gòu)體類型丑蛤。
在objc_msgSend的內(nèi)部,會依次執(zhí)行:
- 檢測selector是否是應(yīng)該忽略的撕阎,比如在Mac OS X開發(fā)中受裹,有了垃圾回收機制,就不會響應(yīng)retain虏束,release這些函數(shù)棉饶。
- 判斷當(dāng)前receiver是否為nil,若為nil镇匀,則不做任何響應(yīng)照藻,即向nil發(fā)送消息,系統(tǒng)不會crash汗侵。
- 檢查Class的method cache幸缕,若cache未命中,則進而查找Class 的 method list晰韵。
- 若在Class 的method list中未找到對應(yīng)的IMP发乔,則進行消息轉(zhuǎn)發(fā)
- 若消息轉(zhuǎn)發(fā)失敗,程序crash
objc_msgSend
objc_msgSend 的偽代碼實現(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方法其實是用匯編寫的列疗。為什么用匯編?一是因為objc_msgSend的返回值類型是可變的浪蹂,需要用到匯編的特性抵栈;二是因為匯編可以提高代碼的效率。
對應(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é)合注釋,還是能夠猜大體意思的缰猴。
首先产艾,系統(tǒng)通過cmp x0, #0檢測receiver是否為nil。如果為nil滑绒,則進入LNilOrTagged闷堡,返回0;
如果不為nil疑故,則現(xiàn)將receiver的isa存入x13寄存器杠览;
在x13寄存器中,取出isa中的class纵势,放到x16寄存器中踱阿;
調(diào)用CacheLookup NORMAL管钳,在這個函數(shù)中,首先查找class的cache软舌,如果未命中才漆,則進入objc_msgSend_uncached。
objc_msgSend_uncached 也是匯編佛点,實現(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是一個匯編的宏定義,其內(nèi)部會調(diào)用C語言函數(shù)_class_lookupMethodAndLoadCache3:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
最終超营,會調(diào)用到lookUpImpOrForward來尋找class的IMP實現(xiàn)或進行消息轉(zhuǎn)發(fā)腺办。
lookUpImpOrForward
lookUpImpOrForward方法的目的在于根據(jù)class和SEL,在class或其super class中找到并返回對應(yīng)的實現(xiàn)IMP糟描,同時怀喉,cache所找到的IMP到當(dāng)前class中。如果沒有找到對應(yīng)IMP船响,lookUpImpOrForward會進入消息轉(zhuǎn)發(fā)流程躬拢。
lookUpImpOrForward 的簡化版實現(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沒有被relize,先relize
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
// 如果class沒有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中查找有無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中沒有找到imp,則依次向上查找super class的方法列表
{
unsigned attempts = unreasonableClassCount();
// 進入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存儲到當(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中沒有找到拴泌,調(diào)用getMethodNoSuper_nolock在super class的方法列表中查找對應(yīng)的實現(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,進入動態(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和動態(tài)方法解析 都不能找到這個imp蚪腐,則進入消息轉(zhuǎn)發(fā)流程,嘗試讓別的class來響應(yīng)這個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;
}
通過上的源碼回季,我們可以很清晰的知曉runtime的消息處理流程:
嘗試在當(dāng)前receiver對應(yīng)的class的cache中查找imp
嘗試在class的方法列表中查找imp
嘗試在class的所有super classes中查找imp(先看Super class的cache,再看super class的方法列表)
上面3步都沒有找到對應(yīng)的imp正林,則嘗試動態(tài)解析這個SEL
動態(tài)解析失敗泡一,嘗試進行消息轉(zhuǎn)發(fā),讓別的class處理這個SEL
在查找class的方法列表中是否有SEL的對應(yīng)實現(xiàn)時觅廓,是調(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;
}
方法實現(xiàn)很簡單鼻忠,就是在class的方法列表methods中,根據(jù)SEL查找對應(yīng)的imp哪亿。
PS:這里順便說一下Category覆蓋類原始方法的問題粥烁,由于在methods中是線性查找的,會返回第一個和SEL匹配的imp蝇棉。而在class的realizeClass方法中讨阻,會調(diào)用methodizeClass來初始化class的方法列表。在methodizeClass方法中篡殷,會將Category方法和class方法合并到一個列表钝吮,同時,會確保Category方法位于class方法前面板辽,這樣奇瘦,在runtime尋找SEL的對應(yīng)實現(xiàn)時,會先找到Category中定義的imp返回劲弦,從而實現(xiàn)了原始方法覆蓋的效果耳标。 關(guān)于Category的底層實現(xiàn),我們會Objective-C runtime機制(4)——深入理解Category中講解邑跪。
關(guān)于消息的查找次坡,可以用下圖更清晰的解釋:
runtime用isa找到receiver對應(yīng)的class,用superClass找到class的父類画畅。
這里用
藍色的表示實例方法的消息查找流程:通過類對象實例的isa查找到對象的class砸琅,進行查找。
用紫色表示類方法的消息查找流程: 通過類的isa找到類對應(yīng)的元類, 沿著元類的super class鏈一路查找轴踱。
關(guān)于元類症脂,我們在上一章中已經(jīng)提及,元類是“類的類”淫僻。因為在runtime中诱篷,類也被看做是一種對象,而對象就一定有其所屬的類雳灵,因此兴蒸,類所屬的類,被稱為類的元類(meta class)细办。
我們所定義的類方法橙凳,其實是存儲在元類的方法列表中的。
我們所定義的類方法笑撞,其實是存儲在元類的方法列表中的岛啸。
這里有一個很好玩的地方,注意到在SEL查找鏈的最上方:Root Class和Root meta Class茴肥。
我們上面說到坚踩,對于類方法,是沿著紫色的路線依次查找Super類方法列表瓤狐。一路上各個節(jié)點瞬铸,都是元類(meta class)批幌。而注意到,Root meta Class的super class竟然是Root class! 也就是說嗓节,當(dāng)在Root meta Class中找不到類方法時荧缘,會轉(zhuǎn)而到Root class中查找類方法。而在Root class中存儲的拦宣,其實都是實例方法截粗。
換句話說,我們在通過類方法的形式調(diào)用Root class中的實例方法鸵隧,在OC中绸罗, 也是可以被解析的!
比如豆瘫,在NSObject中珊蟀,有一個實例方法:
@interface NSObject <NSObject> {
…
- (IMP)methodForSelector:(SEL)aSelector;
…
}
然后,我們自定義類:
@interface MyObj : NSObject
- (void)showY;
@end
@implementation MyObj
- (void)showY {
NSLog(@"AB");
}
@end
我們分別通過類方法的形式調(diào)用methodForSelector 和showY
[MyObj methodForSelector:@selector(test)];
[MyObj showY];
會發(fā)現(xiàn)外驱,編譯器允許methodForSelector的調(diào)用系洛,并能夠正常運行。
而對于showY略步,則會編譯錯誤描扯。
至于原因,就是因為對于Root meta class趟薄,其實會到Root class中尋找對應(yīng)的SEL實現(xiàn)绽诚。
類似的,還有一個好玩的例子杭煎,在子類中恩够,我們重寫NSObject的respondsToSelector方法,然后通過類方法和實例方法兩種形式來調(diào)用羡铲,看看分別會發(fā)生什么情況:
類似的蜂桶,還有一個好玩的例子,在子類中也切,我們重寫NSObject的respondsToSelector方法扑媚,然后通過類方法和實例方法兩種形式來調(diào)用,看看分別會發(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
然后通過類方法和實例方法分別調(diào)用
[MyObj methodForSelector:@selector(test)];
MyObj *obj = [[MyObj alloc] init];
[obj showY];
這兩種調(diào)用方式會有什么不同雷恃?
這當(dāng)做一個思考題疆股,如果大家理解了上面IMP的查找流程,那么應(yīng)該能夠知道答案倒槐。
objc_msgSendSuper
看完了objc_msgSend方法的調(diào)用流程旬痹,我們再來看一下objc_msgSendSuper是如何調(diào)用的。
當(dāng)我們在代碼里面顯示的調(diào)用super 方法時,runtime就會調(diào)用objc_msgSendSuper來完成消息發(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)鍵字時永毅,在這里,super并不代表某個確定的對象人弓,而是編譯器的一個符號沼死,編譯器會將super替換為objc_super *類型來傳入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;
};
第一個成員id receiver票从,表明要將消息發(fā)送給誰漫雕。它應(yīng)該是我們的類實例(注意滨嘱,是當(dāng)前類實例峰鄙,而不是super)
第二個成員Class super_class,表明要到哪里去尋找SEL所對應(yīng)的IMP太雨。它應(yīng)該是我們類實例所對應(yīng)類的super class吟榴。(即要直接到super class中尋找IMP,而略過當(dāng)前class的method list)
簡單來說囊扳,當(dāng)調(diào)用super method時吩翻,runtime會到super class中找到IMP,然后發(fā)送到當(dāng)前class的實例上锥咸。因此狭瞎,雖然IMP的實現(xiàn)是用的super class,但是搏予,最終作用對象熊锭,仍然是當(dāng)前class 的實例。這也就是為什么
NSLog(@"%@ %@",[self class], [super class]);
會輸出同樣的內(nèi)容雪侥,即[self class]的內(nèi)容碗殷。
我們來看一下objc_msgSendSuper的匯編實現(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 中尋找對應(yīng)的IMP速缨,而real receiver則是super->receiver锌妻,即當(dāng)前類實例。
如果在super class的cache中沒有找到IMP的話旬牲,則同樣會調(diào)用__objc_msgSend_uncached仿粹,這和objc_msgSend是一樣的,最終都會調(diào)用到
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
只不過原茅,這里傳入lookUpImpOrForward里面的cls牍陌,使用了super class而已。
動態(tài)解析
如果在類的繼承體系中员咽,沒有找到相應(yīng)的IMP毒涧,runtime首先會進行消息的動態(tài)解析。所謂動態(tài)解析,就是給我們一個機會契讲,將方法實現(xiàn)在運行時動態(tài)的添加到當(dāng)前的類中仿吞。然后,runtime會重新嘗試走一遍消息查找的過程:
// 在class和其所有的super class中均未找到imp捡偏,進入動態(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會調(diào)用_class_resolveMethod银伟,讓用戶進行動態(tài)方法解析你虹,而且設(shè)置標記triedResolver = YES,僅執(zhí)行一次彤避。當(dāng)動態(tài)解析完畢傅物,不管用戶是否作出了相應(yīng)處理,runtime琉预,都會goto retry董饰, 重新嘗試查找一遍類的消息列表。
根據(jù)是調(diào)用的實例方法或類方法圆米,runtime會在對應(yīng)的類中調(diào)用如下方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel // 動態(tài)解析實例方法
+ (BOOL)resolveClassMethod:(SEL)sel // 動態(tài)解析類方法
resolveInstanceMethod
- (BOOL)resolveInstanceMethod:(SEL)sel用來動態(tài)解析實例方法卒暂,我們需要在運行時動態(tài)的將對應(yīng)的方法實現(xiàn)添加到類實例所對應(yīng)的類的消息列表中:
+ (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用于動態(tài)解析類方法。 我們同樣需要將類的實現(xiàn)動態(tài)的添加到相應(yīng)類的消息列表中娄帖。
但這里需要注意也祠,調(diào)用類方法的‘對象’實際也是一個類,而類所對應(yīng)的類應(yīng)該是元類近速。要添加類方法诈嘿,我們必須把方法的實現(xiàn)添加到元類的方法列表中。
在這里数焊,我們就不能夠使用[self class]了永淌,它僅能夠返回當(dāng)前的類。而是需要使用object_getClass(self)佩耳,它其實會返回isa所指向的類遂蛀,即類所對應(yīng)的元類(注意,因為現(xiàn)在是在類方法里面干厚,self所指的是Class李滴,而通過object_getClass(self)獲取self的類,自然是元類)蛮瞄。
+ (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");
}
這里主要弄清楚所坯,類,元類挂捅,實例方法和類方法在不同地方存儲芹助,就清楚了。
關(guān)于class方法和object_getClass方法的區(qū)別:
當(dāng)self是實例對象時,[self class]與object_getClass(self)等價状土,因為前者會調(diào)用后者无蜂,都會返回對象實例所對應(yīng)的類。
當(dāng)self是類對象時蒙谓,[self class]返回類對象自身斥季,而object_getClass(self)返回類所對應(yīng)的元類。
消息轉(zhuǎn)發(fā)
當(dāng)動態(tài)解析失敗累驮,則進入消息轉(zhuǎn)發(fā)流程酣倾。所謂消息轉(zhuǎn)發(fā),是將當(dāng)前消息轉(zhuǎn)發(fā)到其它對象進行處理谤专。
- (id)forwardingTargetForSelector:(SEL)aSelector // 轉(zhuǎn)發(fā)實例方法
+ (id)forwardingTargetForSelector:(SEL)aSelector // 轉(zhuǎn)發(fā)類方法躁锡,id需要返回類對象
- (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沒有實現(xiàn),或返回了nil或self毒租,則會進入另一個轉(zhuǎn)發(fā)流程稚铣。
它會依次調(diào)用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector箱叁,然后runtime會根據(jù)該方法返回的值墅垮,組成一個NSInvocation對象,并調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation 耕漱。注意算色,當(dāng)調(diào)用到forwardInvocation時,無論我們是否實現(xiàn)了該方法螟够,系統(tǒng)都默認消息已經(jīng)得到解析灾梦,不會引起crash。
注意妓笙,和動態(tài)解析不同若河,由于消息轉(zhuǎn)發(fā)實際上是將消息轉(zhuǎn)發(fā)給另一種對象處理。而動態(tài)解析仍是嘗試在當(dāng)前類范圍內(nèi)進行處理寞宫。
消息轉(zhuǎn)發(fā) & 多繼承
通過消息轉(zhuǎn)發(fā)流程萧福,我們可以模擬實現(xiàn)OC的多繼承機制。詳情可以參考 官方文檔 辈赋。
直接調(diào)用IMP
runtime的消息解析鲫忍,究其根本,實際上就是根據(jù)SEL查找到對應(yīng)的IMP钥屈,并調(diào)用之悟民。如果我們可以直接知道IMP的所在,就不用再走消息機制這一層了篷就。似乎不走消息機制會提高一些方法調(diào)用的速度射亏,但現(xiàn)實是這樣的嗎?
我們比較一下:
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打印出來,你會發(fā)現(xiàn)智润,僅僅相差0.000001秒银锻,幾乎可以忽略不計。這樣是因為在消息機制中做鹰,有緩存的存在击纬。
總結(jié)
在本文中,我們了解了OC語言中方法調(diào)用實現(xiàn)的底層機制——消息機制钾麸。并了解了self method和super method的異同更振。
最后,讓我們回答文章開頭的兩個問題:
類實例可以調(diào)用類方法嗎饭尝? 類可以調(diào)用實例方法嗎肯腕? 為什么?
類實例不可用調(diào)用類方法钥平,因為類實例查找消息IMP的流程僅會沿著繼承鏈查找class 的method list实撒,而對于類方法來說,是存于meta class的method list 的涉瘾,因此類實例通過objc_msgSend方法是找不到對應(yīng)的實現(xiàn)的知态。
類大多數(shù)情況下是不能夠調(diào)用實例方法的,除非實例方法定義在root class——NSObject中立叛。因為负敏,當(dāng)調(diào)用類方法時,會在meta class的繼承鏈的method list 查找對應(yīng)的IMP秘蛇,而root meta class對應(yīng)的super class是NSObject其做,因此在NSObject中定義的實例方法,其實是可以通過類方法形式來調(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]; // 這里輸出什么?
...
會輸出
self class = Son, super class = Son