OC - Runtime - Class 結(jié)構(gòu) 和 OC 消息機(jī)制
Runtime 源碼中 Class 結(jié)構(gòu)如下:
// Class 其實(shí)就是一個(gè) struct objc_class *
typedef struct objc_class *Class;
// struct objc_class 繼承 objc_object
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
// objc_object 結(jié)構(gòu)如下
struct objc_object {
isa_t isa;
}
所以Class本身結(jié)構(gòu)如下:
struct objc_class {
isa_t isa;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
Class 內(nèi)部 (bits & FAST_DATA_MASK) 可以得到 (class_rw_t *) 類型恤左,
其內(nèi)部結(jié)構(gòu)如下
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
其中結(jié)構(gòu) class_ro_t 結(jié)構(gòu)如下:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
// strong修飾的ivars
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
// weak修飾的ivars
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
這幾個(gè)類型關(guān)系如下圖
class_rw_t 和 class_ro_t
class_rw_t 里面的 methods辐啄、properties、protocols 是二維數(shù)組屈溉、是可讀可寫的礼殊,包含了類的初始內(nèi)容秦躯,分類的內(nèi)容等
class_ro_t 里面同樣也包含了 baseMethodList砌函、baseProtocols帜消、baseProperties 信息猪勇,他們是一維數(shù)組设褐,是不可讀寫的,他們包含的是 Class 最原始的方法列表信息
method_t
在 class_rw_t 和 class_ro_t 中都包含 class 的各種方法協(xié)議信息泣刹,以方法為例助析,其類型為 method_t.
- method_t 是對方法/函數(shù)的封裝,結(jié)構(gòu)如下
struct method_t {
SEL name; // 函數(shù)名
const char *types; // 編碼(返回值類型椅您,參數(shù)類型)
IMP imp; // 指向函數(shù)的指針(函數(shù)地址)
}
-
IMP
是指向函數(shù)具體實(shí)現(xiàn)的指針外冀。定義id (*IMP)(id, SEL, ...)
。
在 OC 底層的方法調(diào)用掀泳,都會(huì)轉(zhuǎn)化為 C 語言的函數(shù)調(diào)用雪隧,所有的函數(shù)都有兩個(gè)默認(rèn)的參數(shù) self、SEL 其他參數(shù)才是用戶定義方法設(shè)置的參數(shù)员舵,這也是在對象方法內(nèi)我們能直接使用 self 的原因脑沿。
-
SEL
代表方法、函數(shù)名,定義typedef struct objc_selector *SEL;
马僻,通常叫做選擇器庄拇,底層結(jié)構(gòu)和 char* 類似【碌耍可以通過@selector()
和sel_registerName
獲得SEL
type encode
runtime 會(huì)對函數(shù)的返回值參數(shù)進(jìn)行編碼措近。具體來說溶弟,OC 對象的方法,在Runtime底層會(huì)轉(zhuǎn)化為id (*IMP)(id, SEL, ...)
類型指針熄诡。
以get方法為例- (NSString *)name;
其編碼為@16@0:8
以無返回值方法為例- (void)name;
其編碼為v16@0:8
以上兩個(gè)方法對應(yīng)的轉(zhuǎn)換為IMP函數(shù):NSString* name(id self, SEL _cmd)
和 void name(id self, SEL _cmd)
其編碼都是一樣的可很。
編碼的規(guī)則示例如下:
方法緩存 cache_t
Class 內(nèi)部結(jié)構(gòu)有個(gè)方法緩存cache_t
, 用散列表來緩存曾經(jīng)調(diào)用過的方法诗力,可以提高查找速度凰浮。
散列表有一個(gè)起始長度值,會(huì)使用使用到的@selector(sel) & _mask
來緩存具體的方法地址苇本。
當(dāng)列表的容量不夠的時(shí)候會(huì)擴(kuò)容袜茧,直接擴(kuò)大為原來的2倍。
objc_msgSend執(zhí)行流程
消息發(fā)送機(jī)制的執(zhí)行流程主要分為3大階段
- 消息發(fā)送階段 -> 對象方法在Class中找瓣窄,類方法在 MetaClass 中找笛厦,一層層往上找
- 動(dòng)態(tài)方法解析 -> 系統(tǒng)找不到方法,會(huì)給一個(gè)機(jī)會(huì)添加動(dòng)態(tài)方法
- 消息轉(zhuǎn)發(fā)俺夕,如果此步再?zèng)]有操作裳凸,就會(huì)報(bào)錯(cuò)
unrecognized selector sent to instance
objc_msgSend 函數(shù)在 Runtime 的代碼中是直接使用混編語言實(shí)現(xiàn)的,可以從源碼中查到其查找方法流程: 緩存 -> 自己的方法列表 -> 遍歷父類的緩存 & 方法列表劝贸。整個(gè)流程如果都沒有姨谷,就進(jìn)入查找動(dòng)態(tài)方法階段。
如果都沒找到映九,進(jìn)入動(dòng)態(tài)方法解析,動(dòng)態(tài)方法解析流程如下梦湘。動(dòng)態(tài)方法會(huì)根據(jù) + (BOOL)resolveInstanceMethod:(SEL)sel;
查看內(nèi)部有沒有給對應(yīng)的 selector 動(dòng)態(tài)添加方法實(shí)現(xiàn),如果有就重新走消息發(fā)送流程件甥。否則進(jìn)入下一階段:消息轉(zhuǎn)發(fā)捌议。
如果動(dòng)態(tài)解析還是什么都沒有操作,就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段引有。消息轉(zhuǎn)發(fā)階段流程如下瓣颅,這里是閉源的無法從源碼上查看,但是可以從代碼角度驗(yàn)證流程譬正。
#pragma mark - 消息轉(zhuǎn)發(fā)階段 - 1
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSObject objectSpecifier];
}
return [super forwardingTargetForSelector:aSelector];
}
#pragma mark - 消息轉(zhuǎn)發(fā)階段 - 2
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// 這里如果沒有操作就會(huì)走找不到方法宫补。
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 對 invocation 做需要做的事情
}
面試題
- 簡述 OC 對象的消息機(jī)制。
1. OC 中的方法調(diào)用其實(shí)都是轉(zhuǎn)化成了 objc_msgSend 函數(shù)的調(diào)用导帝,給receiver發(fā)送一條消息
2. objc_msgSend 函數(shù)內(nèi)部有3大階段守谓。
1. 消息查找階段
2. 動(dòng)態(tài)消息解析階段
3. 消息轉(zhuǎn)發(fā)階段
- 消息轉(zhuǎn)發(fā)機(jī)制流程
1. 調(diào)用 - (id)forwardingTargetForSelector:(SEL)aSelector 方法,如果有實(shí)現(xiàn)您单,且返回轉(zhuǎn)發(fā)的對象斋荞,就將消息轉(zhuǎn)發(fā)給該對象。反之虐秦,進(jìn)入第二步驟
2. 調(diào)用 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法平酿,如過有針對 aSelector 的方法簽名就繼續(xù)調(diào)用 - (void)forwardInvocation:(NSInvocation *)anInvocation 方法去實(shí)現(xiàn)任何自己想實(shí)現(xiàn)的邏輯凤优。反之進(jìn)入第三步
3. 調(diào)用 doesNotRecognizeedSelector 方法,向外拋出異常蜈彼,經(jīng)典的 unrecognized selector sent to instance 錯(cuò)誤
- Runtime 是什么筑辨,項(xiàng)目中用到過嗎?
是什么:
OC 是一門動(dòng)態(tài)性比較強(qiáng)的語言幸逆,允許很多操作推遲到程序運(yùn)行時(shí)候再進(jìn)行棍辕。
OC 的動(dòng)態(tài)性就是由 Runtime 來支撐和實(shí)現(xiàn)的,Runtime 是一套C語言API还绘,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)楚昭。
平時(shí)編寫的OC代碼,底層都是轉(zhuǎn)換成了 Runtime API 進(jìn)行調(diào)用拍顷。
具體應(yīng)用:
1. 利用關(guān)聯(lián)對象 (AssociatedObject) 給分類添加屬性抚太,創(chuàng)建便利分類,如按鈕直接通過block處理事件
2. 遍歷類的成員變量 (修改textfield的站位文字顏色昔案、字典轉(zhuǎn)模型尿贫、自動(dòng)歸檔等)
3. 交換方法實(shí)現(xiàn)、例如交換系統(tǒng)方法踏揣,eg:自己寫過的一個(gè)監(jiān)聽工具庆亡,hook系統(tǒng)實(shí)現(xiàn),添加自己邏輯
4. 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的問題
- 參考孫源博客呼伸,中有幾道面試題身冀。
下面代碼輸出什么?
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
// 答案: 都是 Son
// 原因: super 是編譯器特性括享,會(huì)被轉(zhuǎn)成 objc_msgSendSuper 函數(shù)搂根,其真實(shí)接收者仍是 self,class 方法是在 NSObject 中實(shí)現(xiàn)的,最終在消息查找會(huì)走到基類的 class 方法铃辖,返回的是消息接收者的類型剩愧,即 self 的類型。
-
super
關(guān)鍵字詳解
一個(gè)Person 類娇斩,其 init 方法如下:
- (instancetype)init
{
self = [super init];
return self;
}
// 使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m -o Person-arm64.cpp 轉(zhuǎn)化為 C++ 代碼如下
static instancetype _I_Person_init(Person * self, SEL _cmd) {
self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
return self;
}
// [super init]; 簡化如下
struct __rw_objc_super {
struct objc_object *object; // 真正的消息接受者
struct objc_object *superClass; // 接收者父類仁卷,作為第一個(gè)查找的類。
};
// __rw_objc_super 結(jié)構(gòu)體
__rw_objc_super objc_super = (__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("Person"))
};
// 實(shí)際上轉(zhuǎn)化為此類型函數(shù)指針 (Person *(*)(__rw_objc_super *, SEL))
objc_msgSendSuper(objc_super, sel_registerName("init"));
super
關(guān)鍵字總結(jié)
-
super
關(guān)鍵字會(huì)轉(zhuǎn)化為objc_msgSendSuper()
犬第,函數(shù). - 其中會(huì)指定锦积,消息接受者,從哪個(gè)類開始查找歉嗓,和要發(fā)送的消息丰介。
-
super
就是要在當(dāng)前對象的父類開始查找消息的實(shí)現(xiàn)。
--- end ---