Objective-C動態(tài)性的根源在方法的調用是通過message
來實現(xiàn)的勋又,一次發(fā)送message
的過程就是一次方法的調用過程苦掘。發(fā)送message
只需要指定對象和SEL
,Runtime
的objc_msgSend
會根據(jù)在信息在對象isa
指針指向的Class
中尋找該SEL
對應的IMP
楔壤,從而完成方法的調用鹤啡。
消息發(fā)送流程
一張圖描述下對象的內存布局
實例對象中存放 isa
指針以及實例變量,有 isa
指針可以找到實例對象所屬的類對象 (類也是對象蹲嚣,面向對象中一切都是對象)揉忘,類中存放著實例方法列表,在這個方法列表中 SEL
作為 key
端铛,IMP
作為 value
。 在編譯時期疲眷,根據(jù)方法名字會生成一個唯一的 Int
標識禾蚕,這個標識就是 SEL
。IMP
其實就是函數(shù)指針 指向了最終的函數(shù)實現(xiàn)狂丝。整個 Runtime
的核心就是 objc_msgSend
函數(shù)换淆,通過給類發(fā)送 SEL
以傳遞消息,找到匹配的 IMP
再獲取最終的實現(xiàn)
類中的 super_class
指針可以追溯整個繼承鏈几颜。向一個對象發(fā)送消息時倍试,Runtime
會根據(jù)實例對象的 isa
指針找到其所屬的類,并自底向上直至根類(NSObject
)中 去尋找SEL
所對應的方法蛋哭,找到后就運行整個方法县习。
metaClass是元類,也有 isa 指針、super_class 指針躁愿。其中保存了類方法列表叛本。
如下是 objc/runtime.h 中定義的類的結構:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 成員變量地址列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法地址列表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址,以避免多次在方法地址列表中查詢彤钟,提升效率
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 遵循的協(xié)議列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
SEL 與 IMP
SEL 可以將其理解為方法的 ID. 結構如下:
typedef struct objc_selector *SEL;
struct objc_selector {
char *name; OBJC2_UNAVAILABLE;
char *types; OBJC2_UNAVAILABLE;
};
IMP 可以理解為函數(shù)指針来候,指向了最終的實現(xiàn)。
SEL 與 IMP 的關系非常類似于 HashTable 中 key 與 value 的關系逸雹。OC 中不支持函數(shù)重載的原因就是因為一個類的方法列表中不能存在兩個相同的 SEL 营搅。但是多個方法卻可以在不同的類中有一個相同的 SEL,不同類的實例對象執(zhí)行相同的 SEL 時梆砸,會在各自的方法列表中去根據(jù) SEL 去尋找自己對應的IMP转质。這使得OC可以支持函數(shù)重寫。
消息傳遞機制
當一個對象 sender 調用代碼時辫樱,實際上是調用了runtime的objc_msgSend函數(shù)峭拘,所以OC的方法調用并不像C函數(shù)一樣能按照地址直接取用,而是經過了一系列的過程狮暑。
下面將通過代碼驗證下鸡挠,新建一個工程,創(chuàng)建一個繼承于NSObject的子類(RuntimeTest)搬男,.m文件中寫入一下代碼(驗證 - (void)changeVaule:(NSString *)vauleString
如何調用拣展,工具Xcode10)
@implementation RuntimeTest
- (void)test:(NSString *)vaule {
[self changeVaule:vaule];
}
- (void)changeVaule:(NSString *)vauleString {
}
@end
開啟終端,進入工程存放文件目錄缔逛,使用clang將RuntimeTest.m
文件轉換成RuntimeTest.cpp
文件查看其C函數(shù)調用實現(xiàn)
clang -rewrite-objc RuntimeTest.m
然后從RuntimeTest.cpp轉換后實現(xiàn)备埃,下面??是截取部分類容:
static void _I_RuntimeTest_test_(RuntimeTest * self, SEL _cmd, NSString * _Nonnull vaule) {
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("changeVaule:"), (NSString * _Nonnull)vaule);
}
static void _I_RuntimeTest_changeVaule_(RuntimeTest * self, SEL _cmd, NSString *vauleString) {
}
去掉強制轉換,可以看出最終調用方法是:
objc_msgSend(self, sel_registerName("changeVaule:"), vaule);
其中:
-
sel_registerName("changeVaule:")
是一個SEL類型的值褐奴,用于標示類中的一個方法(類比C的函數(shù)指針來理解) -
sel_registerName(methodName)
表達式用于獲得當前類中methodName方法的對應SEL
obj_msgSend(recevier, selector, ...)函數(shù)的主要執(zhí)行流程大致是:
根據(jù)SEL
,首先在Class
中的緩存查找imp
(沒緩存則初始化緩存)按脚,如果沒找到,則向父類的Class
查找敦冬。如果一直查找到根類仍舊沒有實現(xiàn)辅搬,則用_objc_msgForward
函數(shù)指針代替imp
。最后脖旱,執(zhí)行這個imp
堪遂。
_objc_msgForward
是用于消息轉發(fā)的。這個函數(shù)的實現(xiàn)并沒有在objc-runtime
的開源代碼里面萌庆,而是在Foundation
框架里面實現(xiàn)的溶褪。加上斷點啟動程序后,會發(fā)現(xiàn)__CFInitialize
這個方法會調用objc_setForwardHandler
函數(shù)來注冊一個實現(xiàn)践险。