完整的程序生命階段可以大致分為編輯,編譯,鏈接,分發(fā),安裝,加載和運(yùn)行這幾個(gè)階段。 Runtime 在廣義上是指程序正在運(yùn)行的階段。但是在iOS開(kāi)發(fā)中提到 Runtime 則更多的是指 Objective-C 中用匯編和C語(yǔ)言寫(xiě)的 Runtime 庫(kù)以及它帶來(lái)的一些特性與功能。
Objective-C 是基于面向過(guò)程開(kāi)發(fā)的 C語(yǔ)言的,它通過(guò) Runtime 庫(kù)維護(hù)了一個(gè)運(yùn)行時(shí)系統(tǒng),可以在程序運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建類(lèi)和對(duì)象,在方法調(diào)用時(shí)進(jìn)行消息的傳遞和轉(zhuǎn)發(fā)進(jìn)而找出方法的最終執(zhí)行代碼等等,使得Object-C 的代碼更為靈活,賦予了 Objective-C 面向?qū)ο蟮奶匦砸约皠?dòng)態(tài)性。
由于Runtime在Objective-C 中的運(yùn)用十分廣泛,本文只針對(duì)基礎(chǔ)的類(lèi)對(duì)象結(jié)構(gòu)和消息傳遞機(jī)制進(jìn)行分析。
?Class
在 Objective-C 中類(lèi)用 Class 類(lèi)型表示,它實(shí)際上是一個(gè)指向 objc_class 結(jié)構(gòu)體的指針。而 objc_class 結(jié)構(gòu)體定義在Runtime 庫(kù)中。
NSObject 是 Objectiv-C 語(yǔ)言中幾乎所有對(duì)象的根類(lèi)。它的定義如下
@interface NSObject <NSObject> {
? ? Class isa? OBJC_ISA_AVAILABILITY;?
}
Class 實(shí)際上是一個(gè)指向 objc_class 結(jié)構(gòu)體的指針。它定義在 Obsolete Source/Object.mm 中:
typedef struct objc_class *Class;
其實(shí)不只是類(lèi),Objective-C? 中類(lèi)的實(shí)例對(duì)象,方法,分類(lèi),協(xié)議等,在 Runtime 庫(kù)中都是用結(jié)構(gòu)體表示。因?yàn)?Objectiv-C 是一門(mén)面向?qū)ο箝_(kāi)發(fā)的高級(jí)編程語(yǔ)言。在轉(zhuǎn)變成計(jì)算機(jī)能夠識(shí)別的機(jī)器語(yǔ)言之前,它需要先轉(zhuǎn)換成純C語(yǔ)言,進(jìn)而再進(jìn)行編譯和匯編操作。 Runtime 庫(kù) 實(shí)現(xiàn)了Objective-C? 到 C 語(yǔ)言的轉(zhuǎn)換。
查看 objc-runtime-new.h 中 objc_class 結(jié)構(gòu)體的定義:
//對(duì)象結(jié)構(gòu)體(類(lèi)對(duì)象和實(shí)例對(duì)象都是這個(gè)類(lèi)型,類(lèi)本身是一個(gè)類(lèi)對(duì)象)
struct objc_object {
private:
? ? isa_t isa; //類(lèi)對(duì)象的isa指向元類(lèi),實(shí)例對(duì)象的isa指向類(lèi)對(duì)象
? ? //省略objc_objec的方法
? ? .......
?};
//objc_class繼承于objc_object,因此類(lèi)其實(shí)也是一個(gè)對(duì)象
struct objc_class : objc_object {
? ? Class superclass;? ? ? ? ? //指向父類(lèi)
? ? cache_t cache;? ? ? ? ? ? //實(shí)例方法緩存?
? ? class_data_bits_t bits;? ? // 存儲(chǔ)與類(lèi)有關(guān)的信息
? ? class_rw_t* data() {? ? //存儲(chǔ)實(shí)例對(duì)象的屬性,方法,協(xié)議等信息
?? return bits.data();
}
? ? //省略objc_class的方法
? ? .......
};
以下介紹一下objc_class結(jié)構(gòu)體中的比較重要的幾個(gè)屬性。
?元類(lèi)(Meta Class)
在 objc_class 結(jié)構(gòu)體繼承自objc_object, objc_object 中有定義了一個(gè)isa屬性。類(lèi)對(duì)象的isa指向的是它的元類(lèi)meta-class。objc-runtime.mm中提供了根據(jù)類(lèi)名獲取 meta-class 的方法。從方法的中可以看出實(shí)際返回的就是類(lèi)指針。并且從返回值類(lèi)型表明了 meta-class 其實(shí)也是一個(gè) Class 類(lèi)型,即一個(gè) objc_class 類(lèi)型的結(jié)構(gòu)體。
//獲取元類(lèi)的方法實(shí)現(xiàn)如下:
Class objc_getMetaClass(const char *aClassName)
{
? ? Class cls;
? ? if (!aClassName) return Nil;
? ? cls = objc_getClass (aClassName);
? ? if (!cls)
? ? {
? ? ? ? _objc_inform ("class `%s' not linked into application", aClassName);
? ? ? ? return Nil;
? ? }
? ? return cls->ISA();
}
meta-class 存儲(chǔ)著一個(gè)類(lèi)的所有類(lèi)方法。所有直接或者間接繼承自 NSObject 的類(lèi)都會(huì)有一個(gè)自己的 meta-class ,因?yàn)槊總€(gè)類(lèi)的類(lèi)方法基本不可能完全相同。
現(xiàn)有Person類(lèi),本類(lèi)中有類(lèi)方法fire以及在分類(lèi)中聲明的類(lèi)方法growUp。以下為查找類(lèi)方法的存儲(chǔ)位置的過(guò)程
(lldb) p (objc_class *)0x1000013f8? //Person類(lèi)在內(nèi)存中的地址
(objc_class *) $0 = 0x00000001000013f8
(lldb) p $0->isa? //獲取Person的isa
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(Class) $1 = 0x00000001000013d0
(lldb) p (class_data_bits_t *)0x00000001000013f0 //person元類(lèi)對(duì)象的bits
(class_data_bits_t *) $12 = 0x00000001000013f0
(lldb) p $12->data()? //獲取元類(lèi)對(duì)象的class_rw_t *
(class_rw_t *) $13 = 0x00000001000011e8
(lldb) p (class_ro_t *)$13
(class_ro_t *) $14 = 0x00000001000011e8
(lldb) p *$14
(class_ro_t) $15 = {
? flags = 389
? instanceStart = 40
? instanceSize = 40
? reserved = 0
? ivarLayout = 0x0000000000000000
? name = 0x0000000100000f03 "Person"?
? baseMethodList = 0x00000001000011b0
? baseProtocols = 0x0000000000000000
? ivars = 0x0000000000000000
? weakIvarLayout = 0x0000000000000000
? baseProperties = 0x0000000000000000
}
(lldb) p $15.baseMethodList? //元類(lèi)對(duì)象的方法列表
(method_list_t *) $16 = 0x00000001000011b0
(lldb) p $16->get(0)? //###### Person的類(lèi)方法fire? ########
(method_t) $17 = {
? name = "fire"
? types = 0x0000000100000f72 "v16@0:8"
? imp = 0x0000000100000e10 (testRuntime`+[Person(SuperMan) fire] at Person+SuperMan.m:15)
}
(lldb) p $16->get(1)
(method_t) $18 = {
? name = "growUp"? //Person的類(lèi)方法grpwUp
? types = 0x0000000100000f72 "v16@0:8"
? imp = 0x0000000100000be0 (testRuntime`+[Person growUp] at Person.m:18)
}
(lldb) p $16->get(2)
Assertion failed: (i < count), function get, file /Users/gwj/Downloads/RuntimeSourceCode-master/objc4-750.1/runtime/objc-runtime-new.h, line 116.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
```
- ### isa與superclass
?objc_object 結(jié)構(gòu)體的定義:
```c
//對(duì)象結(jié)構(gòu)體(類(lèi)和實(shí)例對(duì)象都是這個(gè)類(lèi)型,類(lèi)本身是一個(gè)類(lèi)對(duì)象)
struct objc_object {
private:
? ? isa_t isa; //類(lèi)的isa指向元類(lèi),實(shí)例對(duì)象的isa指向類(lèi)對(duì)象
? ? //省略objc_objec的方法
? ? .......
?};
實(shí)例對(duì)象是 objc_object類(lèi)型,類(lèi) objc_class 繼承自 objc_object 類(lèi)型,因此類(lèi)和實(shí)例對(duì)象都有一個(gè) isa 屬性。
實(shí)例對(duì)象的 isa 指向的是類(lèi)對(duì)象,類(lèi)的isa指向的是 meta-class,meta-class 的isa指向的是NSObject的meta-class。而NSObject的meta-class的isa指向的是它本身,整個(gè)體系構(gòu)成了一個(gè)閉環(huán)。
類(lèi)還有一個(gè)的supperclass屬性,它指向父類(lèi),meta-class的superclass指向父類(lèi)的meta-class。
Objective - C 消息傳遞機(jī)制與這樣的繼承體系是密不可分的。具體在消息傳遞的章節(jié)中再做介紹。

為了驗(yàn)證的isa指針的指向, 現(xiàn)有Person,Student,NSObject3個(gè)類(lèi),Person與Student分別繼承自NSObject
(lldb) p (objc_class *)0x1000013f8?
(objc_class *) $0 = 0x00000001000013f8
(lldb) p $0->isa? //獲取Person的isa
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(Class) $1 = 0x00000001000013d0
(lldb) p (objc_class *)$1? //獲取Person的meta-class
(objc_class *) $2 = 0x00000001000013d0?
(lldb) p $2->isa? ? //獲取Person的meta-class的isa
(Class) $3 = 0x0000000100afd0f0? //#####Person的meta-class的isa#######
(lldb) p (objc_class *)0x100001448
(objc_class *) $4 = 0x0000000100001448
(lldb) p $4->isa? //獲取Student的isa
(Class) $5 = 0x0000000100001420
(lldb) p (objc_class *)$5
(objc_class *) $6 = 0x0000000100001420
(lldb) p $6->isa
(Class) $7 = 0x0000000100afd0f0? //#####Student的meta-class的isa#######
(lldb) p (objc_class *)0x100afd140
(objc_class *) $8 = 0x0000000100afd140
(lldb) p $8->isa?
(Class) $9 = 0x0000000100afd0f0 //NSObject的meta-class
(lldb) p (objc_class *)$9
(objc_class *) $10 = 0x0000000100afd0f0 //NSObject的meta-class的isa
(lldb) p $10->isa
(Class) $11 = 0x0000000100afd0f0
?成員變量(ivar)及屬性(Property)
在 objc_class 中實(shí)例對(duì)象相關(guān)的很多信息都存儲(chǔ)在了 class_rw_t? 類(lèi)型的數(shù)據(jù)結(jié)構(gòu)中,其中包括了屬性列表和成員列表,因此在介紹class_rw_t 之前先介紹一下這兩種數(shù)據(jù)類(lèi)型。
//屬性
struct property_t {
? ? const char *name;
? ? const char *attributes;
};
//成員變量
struct ivar_t {
? ? int32_t *offset;
? ? const char *name;
? ? const char *type;
? ? // alignment is sometimes -1; use alignment() instead
? ? uint32_t alignment_raw;
? ? uint32_t size;
? ? uint32_t alignment() const {
? ? ? ? if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
? ? ? ? return 1 << alignment_raw;
? ? }
};
成員變量
在類(lèi)的 .m 文件中直接聲明的則只會(huì)生成帶下劃線(xiàn)的成員變量,沒(méi)有 set 和get方法。成員變量結(jié)構(gòu)體中的信息中包括了屬性在內(nèi)存中的位置,大小等。
屬性
在一個(gè)類(lèi)中用 @property 聲明的是屬性,會(huì)自動(dòng)生成對(duì)應(yīng)的 set 和 get 方法用于屬性的賦值和取值,以及一個(gè)帶下劃線(xiàn)的同名成員變量。屬性存儲(chǔ)在 class_ro_t 結(jié)構(gòu)的baseProperties以及 class_rw_t property_array_t中。
本類(lèi)中的所有成員變量的信息會(huì)存放在 class_ro_t 的 ivars 中的。一個(gè)類(lèi)的實(shí)例對(duì)象大小在編譯時(shí)期就確定了,就是根據(jù)每個(gè)成員變量的大小,進(jìn)行內(nèi)存對(duì)齊后計(jì)算得出的。
?class_rw_t 與class_ro_t
class_rw_t 與 class_ro_t 的定義如下:
// 類(lèi)的方法、屬性、協(xié)議等信息都保存在class_rw_t結(jié)構(gòu)體中
struct class_rw_t {
? ? // Be warned that Symbolication knows the layout of this structure.
? ? uint32_t flags;
? ? uint32_t version; //版本信息
? ? const class_ro_t *ro;//存儲(chǔ)了類(lèi)在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議等,是只讀類(lèi)型
? ? method_array_t methods;? // 方法信息
? ? property_array_t properties; // 屬性信息
? ? protocol_array_t protocols;? // 協(xié)議信息
? ? Class firstSubclass;
? ? Class nextSiblingClass;
? ? char *demangledName;
?? ? //省略class_rw_t的方法
? ? .......
? };
struct class_ro_t {
? ? uint32_t flags;
? ? uint32_t instanceStart;
? ? uint32_t instanceSize; //實(shí)例對(duì)象的大小
#ifdef __LP64__
? ? uint32_t reserved;
#endif
? ? const uint8_t * ivarLayout; //成員變量布局
? ? const char * name; //類(lèi)名
? ? method_list_t * baseMethodList; // 方法列表
? ? protocol_list_t * baseProtocols; // 協(xié)議列表
? ? const ivar_list_t * ivars;? // 成員變量列表
? ? const uint8_t * weakIvarLayout;?
? ? property_list_t *baseProperties; // 屬性列表
? ? method_list_t *baseMethods() const {
? ? ? ? return baseMethodList;
? ? }
};
在前面介紹 objc_class 時(shí)可以看到它有一個(gè) class_rw_t* data(),這個(gè)方法返回了一個(gè)指向 class_rw_t 類(lèi)型的指針,而類(lèi)的實(shí)例對(duì)象相關(guān)的信息大部分都存儲(chǔ)在這個(gè)指針指向的內(nèi)存中。例如實(shí)例對(duì)象的屬性,實(shí)例方法以及分類(lèi)中添加信息,協(xié)議信息等等。class_rw_t 的數(shù)據(jù)都是在運(yùn)行時(shí)才確定下來(lái)的。
class_rw_t 中有一個(gè)指向class_ro_t 類(lèi)型的指針 ro,其中存儲(chǔ)了當(dāng)前類(lèi)在編譯期就確定的一些信息。通過(guò)class_rw_t 和 class_ro_t? 的定義可以發(fā)現(xiàn),它們都存儲(chǔ)的信息有部分的重合。那么它們之間具體是有什么聯(lián)系呢?我們可以通過(guò)下列的例子做一個(gè)論證。
現(xiàn)有Person類(lèi),及它的分類(lèi)。在分類(lèi)中添加了方法,且使用Runtime庫(kù)提供 方法添加了一個(gè)title屬性。
//Person類(lèi)
@interface Person : NSObject
@property NSString *name;
@property int sex;
- (void)eat;
+ (void)growUp;
@end
#import "Person.h"
@implementation Person
float _height;
//省略方法實(shí)現(xiàn)
@end
//Person分類(lèi)
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (SuperMan)
@property(nonatomic,copy) NSString *title;
- (void)fly;
+ (void)fire;
@end
//省略分類(lèi)的.m
//獲取類(lèi)對(duì)象地址
int main(int argc, const char * argv[]) {
? ? @autoreleasepool {
? ? ? ? Class p = [Person class];
? ? ? ? NSLog(@"%p", p); //0x100002540
? ? }
? ? return 0;
}
在 Runtime庫(kù)的入口方法 _objc_init() 中打斷點(diǎn),這個(gè)時(shí)候整個(gè)運(yùn)行時(shí)系統(tǒng)還未初始化,類(lèi)對(duì)象的初始化方法也還沒(méi)走,此時(shí)拿到的 Person類(lèi)對(duì)象是編譯后的產(chǎn)物,它有一部分?jǐn)?shù)據(jù),但是并不是一個(gè)完整的類(lèi)對(duì)象。

因?yàn)轭?lèi)在內(nèi)存中的地址在編譯時(shí)就已經(jīng)確定,不更改類(lèi)的信息或者添加新的類(lèi),地址不會(huì)改變。所以可以先打印一遍Person類(lèi)的地址,然后在重新運(yùn)行項(xiàng)目,在上述斷點(diǎn)位置使用該地址獲取Person類(lèi)對(duì)象的詳細(xì)信息。
(lldb) p (objc_class *)0x100002540?
(objc_class *) $0 = 0x0000000100002540
(lldb) p (class_data_bits_t *)0x0000000100002560? //類(lèi)對(duì)象地址偏移32位獲取class_data_bits_t *
(class_data_bits_t *) $1 = 0x0000000100002560
(lldb) p $1->data()? //調(diào)用bits.data()獲取class_rw_t *
(class_rw_t *) $2 = 0x00000001000023c8
(lldb) p *$2 //此時(shí)直接讀取class_rw_t *的內(nèi)容會(huì)報(bào)警告說(shuō)沒(méi)有數(shù)據(jù)
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(class_rw_t) $3 = {
? flags = 388
? version = 8
? ro = 0x0000000000000018
? methods = {
? ? list_array_tt = {
?? ? ? = {
? ? ? ? list = 0x0000000100001ebe
? ? ? ? arrayAndFlag = 4294975166
? ? ? }
? ? }
? }
? properties = {
? ? list_array_tt = {
?? ? ? = {
? ? ? ? list = 0x0000000100001eb7
? ? ? ? arrayAndFlag = 4294975159
? ? ? }
? ? }
? }
? protocols = {
? ? list_array_tt = {
?? ? ? = {
? ? ? ? list = 0x00000001000021c8
? ? ? ? arrayAndFlag = 4294975944
? ? ? }
? ? }
? }
? firstSubclass = nil
? nextSiblingClass = 0x0000000100002360
? demangledName = 0x0000000000000000
}
(lldb) p $3.ro? //訪(fǎng)問(wèn)class_rw_t *中ro,發(fā)現(xiàn)并不能訪(fǎng)問(wèn)
(const class_ro_t *) $4 = 0x0000000000000018
(lldb) p *$4
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
(lldb) p $3.methods? //訪(fǎng)問(wèn)class_rw_t *中methods,最終發(fā)現(xiàn)里面沒(méi)有方法
(method_array_t) $6 = {
? list_array_tt = {
?? ? = {
? ? ? list = 0x0000000100001ebe
? ? ? arrayAndFlag = 4294975166
? ? }
? }
}
(lldb) p $6.list
(method_list_t *) $7 = 0x0000000100001ebe
(lldb) p $7->get(0)
(method_t) $8 = {
? name =
? types = 0x55776f726700746e
? imp = 0x632e007461650070 (0x632e007461650070)
}
(lldb) p $7->get(1)
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
(lldb) p (class_ro_t *)$2? //將上面調(diào)用bits.data()獲取的class_rw_t *強(qiáng)轉(zhuǎn)成class_ro_t *
(class_ro_t *) $10 = 0x00000001000023c8
(lldb) p *$10? //讀取class_ro_t *中的數(shù)據(jù)
(class_ro_t) $11 = {
? flags = 388
? instanceStart = 8
? instanceSize = 24
? reserved = 0
? ivarLayout = 0x0000000100001ebe "\x11"
? name = 0x0000000100001eb7 "Person"
? baseMethodList = 0x00000001000021c8
? baseProtocols = 0x0000000000000000
? ivars = 0x0000000100002360
? weakIvarLayout = 0x0000000000000000
? baseProperties = 0x00000001000022e0
}
(lldb) p $11.ivars //ivars中有用Person類(lèi)中聲明的屬性與成員變量
(const ivar_list_t *) $12 = 0x0000000100002360
(lldb) p $12->get(0)
(ivar_t) $13 = {
? offset = 0x0000000100002510
? name = 0x0000000100001f04 "_height"
? type = 0x0000000100001f9c "f"
? alignment_raw = 2
? size = 4
}
(lldb) p $12->get(1)
(ivar_t) $14 = {
? offset = 0x0000000100002508
? name = 0x0000000100001f0c "_sex"
? type = 0x0000000100001f9e "i"
? alignment_raw = 2
? size = 4
}
(lldb) p $12->get(2)
(ivar_t) $15 = {
? offset = 0x0000000100002500
? name = 0x0000000100001f11 "_name"
? type = 0x0000000100001fa0 "@"NSString""
? alignment_raw = 3
? size = 8
}
(lldb) p $12->get(3)
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
Assertion failed: (i < count), function get, file /Users/gwj/Desktop/RuntimeSourceCode-master/objc4-750.1/runtime/objc-runtime-new.h, line 116.
(lldb) p $10.baseProperties? //basePRoperties中有用@property聲明的屬性以及用分類(lèi)中添加的屬性
(property_list_t *) $16 = 0x00000001000022e0
? Fix-it applied, fixed expression was:?
? ? $10->baseProperties
(lldb) p $10->baseProperties
(property_list_t *) $17 = 0x00000001000022e0
(lldb) p $17->get(0)
(property_t) $18 = (name = "title", attributes = "T@"NSString",C,N")
(lldb) p $17->get(1)
(property_t) $19 = (name = "name", attributes = "T@"NSString",&,V_name")
(lldb) p $17->get(2)
(property_t) $20 = (name = "sex", attributes = "Ti,V_sex")
(lldb) p $17->get(3)
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
Assertion failed: (i < count), function get, file /Users/gwj/Desktop/RuntimeSourceCode-master/objc4-750.1/runtime/objc-runtime-new.h, line 116.
(lldb) p $10.baseMethodList? //方法列表中有Person本類(lèi)及分類(lèi)中聲明的對(duì)象方法,以及屬性的set,get方法
(method_list_t *) $21 = 0x00000001000021c8
? Fix-it applied, fixed expression was:?
? ? $10->baseMethodList
(lldb) p $10->baseMethodList
(method_list_t *) $22 = 0x00000001000021c8
(lldb) p $22->get(0)
(method_t) $23 = {
? name = "setTitle:"
? types = 0x0000000100001f7e "v24@0:8@16"
? imp = 0x0000000100001bd0 (testRuntime`-[Person(SuperMan) setTitle:] at Person+SuperMan.m:14)
}
(lldb) p $22->get(1)
(method_t) $24 = {
? name = "title"
? types = 0x0000000100001f76 "@16@0:8"
? imp = 0x0000000100001c40 (testRuntime`-[Person(SuperMan) title] at Person+SuperMan.m:18)
}
(lldb) p $22->get(2)
(method_t) $25 = {
? name = "fly"
? types = 0x0000000100001f6e "v16@0:8"
? imp = 0x0000000100001ca0 (testRuntime`-[Person(SuperMan) fly] at Person+SuperMan.m:24)
}
(lldb) p $22->get(3)
(method_t) $26 = {
? name = "eat"
? types = 0x0000000100001f6e "v16@0:8"
? imp = 0x00000001000018a0 (testRuntime`-[Person eat] at Person.m:14)
}
(lldb) p $22->get(4)
(method_t) $27 = {
? name = ".cxx_destruct"
? types = 0x0000000100001f6e "v16@0:8"
? imp = 0x00000001000019c0 (testRuntime`-[Person .cxx_destruct] at Person.m:10)
}
(lldb) p $22->get(5)
(method_t) $28 = {
? name = "name"
? types = 0x0000000100001f76 "@16@0:8"
? imp = 0x0000000100001900 (testRuntime`-[Person name] at Person.h:13)
}
(lldb) p $22->get(6)
(method_t) $29 = {
? name = "setName:"
? types = 0x0000000100001f7e "v24@0:8@16"
? imp = 0x0000000100001930 (testRuntime`-[Person setName:] at Person.h:13)
}
(lldb) p $22->get(7)
(method_t) $30 = {
? name = "sex"
? types = 0x0000000100001f89 "i16@0:8"
? imp = 0x0000000100001970 (testRuntime`-[Person sex] at Person.h:14)
}
(lldb) p $22->get(8)
(method_t) $31 = {
? name = "setSex:"
? types = 0x0000000100001f91 "v20@0:8i16"
? imp = 0x0000000100001990 (testRuntime`-[Person setSex:] at Person.h:14)
}
(lldb) p $22->get(9)
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
Assertion failed: (i < count), function get, file /Users/gwj/Desktop/RuntimeSourceCode-master/objc4-750.1/runtime/objc-runtime-new.h, line 116.
(lldb)
綜上可以確定,編譯完成后,每個(gè)類(lèi)會(huì)有一個(gè)信息并不完整的類(lèi)對(duì)象。這個(gè)類(lèi)對(duì)象的class_rw_t * 指向的真實(shí)類(lèi)型是class_ro_t? ,它里面存儲(chǔ)了類(lèi)對(duì)象在編譯時(shí)就能確定的信息,如instancesize,屬性列表,成員列表,方法列表,以及遵循的協(xié)議信息等。
在runtime的初始化方法(objc_init)中會(huì)調(diào)用_dyld_objc_notify_register 方法,接著會(huì)執(zhí)行類(lèi)對(duì)象的初始化方法。這個(gè)方法將使用編譯時(shí)產(chǎn)生 class_ro_t中數(shù)據(jù)來(lái)完成類(lèi)對(duì)象真正的初始化,并且會(huì)將 class_ro_t 中的方法列表,屬性列表,協(xié)議列表等賦值給類(lèi)對(duì)象的class_rw_t 的對(duì)應(yīng)屬性,并且將這個(gè) class_ro_t 賦值給它的ro賦值,作為一個(gè)只讀屬性。如果后期在運(yùn)行時(shí)為類(lèi)對(duì)象動(dòng)態(tài)添加方法和屬性的話(huà),都是添加到 class_rw_t 中,不會(huì)影響只讀結(jié)構(gòu)ro。
以下是 objc-runtime-new.mm 中有類(lèi)對(duì)象的初始化方法。
// 類(lèi)對(duì)象的初始化方法
static Class realizeClass(Class cls)
{
? ? runtimeLock.assertLocked();
? ? const class_ro_t *ro;
? ? class_rw_t *rw;
? ? Class supercls;
? ? Class metacls;
? ? bool isMeta;
? ? //省略無(wú)關(guān)代碼
? ? //........
? ? //通過(guò)類(lèi)對(duì)象調(diào)用data(),并將返回結(jié)果從class_rw_t *強(qiáng)轉(zhuǎn)成 class_ro_t *
? ? ro = (const class_ro_t *)cls->data();
? ? //省略無(wú)關(guān)代碼
? ? //........
? ? if (ro->flags & RO_FUTURE) {
? ? ? ? // rw結(jié)構(gòu)體已經(jīng)被初始化(正常不會(huì)執(zhí)行到這里)
? ? ? ? // This was a future class. rw data is already allocated.
? ? ? ? rw = cls->data();
? ? ? ? ro = cls->data()->ro;
? ? ? ? cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
? ? } else {
? ? ? ? // 絕大部分的類(lèi)都是執(zhí)行到這里
? ? ? ? // 初始化一個(gè)新的class_rw_t結(jié)構(gòu)體
? ? ? ? rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
? ? ? ? //給rw的ro賦值
? ? ? ? rw->ro = ro;
? ? ? ? rw->flags = RW_REALIZED|RW_REALIZING;
? ? ? ? // cls->data 指向class_rw_t結(jié)構(gòu)體
? ? ? ? cls->setData(rw);
? ? }
? ? //省略無(wú)關(guān)代碼
? ? //........
? ? isMeta = ro->flags & RO_META;
? ? rw->version = isMeta ? 7 : 0;? // old runtime went up to 6
? ? supercls = realizeClass(remapClass(cls->superclass));
? ? metacls = realizeClass(remapClass(cls->ISA()));
? ? //省略無(wú)關(guān)代碼
? ? //........
? ? cls->superclass = supercls;
? ? cls->initClassIsa(metacls);
? ? //省略無(wú)關(guān)代碼
? ? //........
? ? // 此時(shí)cls里的rw.ro已經(jīng)有值了,在這個(gè)方法里就會(huì)用將ro中的類(lèi)實(shí)現(xiàn)的方法(包括分類(lèi))、屬性和遵循的協(xié)議添加到class_rw_t結(jié)構(gòu)體中的methods、properties、protocols列表中
? ? methodizeClass(cls);
? ? return cls;
}
// 設(shè)置類(lèi)的方法列表、協(xié)議列表、屬性列表,包括category的方法
static void methodizeClass(Class cls)
{
? ? runtimeLock.assertLocked();
? ? bool isMeta = cls->isMetaClass();
? ? auto rw = cls->data();
? ? auto ro = rw->ro;
? ? // 省略無(wú)關(guān)代碼
? ? //........
? ? // 將class_ro_t中的baseMethods添加到class_rw_t中的methods
? ? method_list_t *list = ro->baseMethods();
? ? if (list) {
? ? ? ? prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
? ? ? ? rw->methods.attachLists(&list, 1);
? ? }
? ? // 將class_ro_t中的baseProperties添加到class_rw_t中的properties
? ? property_list_t *proplist = ro->baseProperties;
? ? if (proplist) {
? ? ? ? rw->properties.attachLists(&proplist, 1);
? ? }
? ? // 將class_ro_t中的baseProtocols添加到class_rw_t的protocols
? ? protocol_list_t *protolist = ro->baseProtocols;
? ? if (protolist) {
? ? ? ? rw->protocols.attachLists(&protolist, 1);
? ? }
? ? // 省略無(wú)關(guān)代碼
? ? //........
? ? // 添加category方法
? ? category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
? ? attachCategories(cls, cats, false /*don't flush caches*/);
? ? // 省略無(wú)關(guān)代碼
? ?//........
? ? if (cats) free(cats);
}
在realizeClass方法最后return cls的地方打斷點(diǎn),設(shè)置斷點(diǎn)條件是當(dāng)cls為Person對(duì)象時(shí)會(huì)執(zhí)行斷點(diǎn)。當(dāng)獲取到Person類(lèi)對(duì)象cls時(shí),再一次打印其中的數(shù)據(jù)就會(huì)發(fā)現(xiàn)里面的 class_rw_t * 中的ro以及methods等數(shù)據(jù)已經(jīng)有內(nèi)容了。因?yàn)轭?lèi)對(duì)象的初始化已經(jīng)完成了。
//省略其余打印
//.....
(lldb) p $1048->data()
(class_rw_t *) $1050 = 0x00000001014384d0
(lldb) p *$1050
(class_rw_t) $1051 = {
? flags = 2148139008
? version = 0
? ro = 0x00000001000012c0
? methods = {
? ? list_array_tt = {
?? ? ? = {
? ? ? ? list = 0x0000000100001100
? ? ? ? arrayAndFlag = 4294971648
? ? ? }
? ? }
? }
? properties = {
? ? list_array_tt = {
?? ? ? = {
? ? ? ? list = 0x0000000100001298
? ? ? ? arrayAndFlag = 4294972056
? ? ? }
? ? }
? }
? protocols = {
? ? list_array_tt = {
?? ? ? = {
? ? ? ? list = 0x0000000000000000
? ? ? ? arrayAndFlag = 0
? ? ? }
? ? }
? }
? firstSubclass = nil
? nextSiblingClass = 0x00007fff87b3dc80
? demangledName = 0x0000000000000000
}
(lldb) p $1051.ro
(const class_ro_t *) $1052 = 0x00000001000012c0
(lldb) p *$1052
(const class_ro_t) $1053 = {
? flags = 388
? instanceStart = 8
? instanceSize = 24
? reserved = 0
? ivarLayout = 0x0000000100000f0a "\x11"
? name = 0x0000000100000f03 "Person"
? baseMethodList = 0x0000000100001100
? baseProtocols = 0x0000000000000000
? ivars = 0x0000000100001230
? weakIvarLayout = 0x0000000000000000
? baseProperties = 0x0000000100001298
}
(lldb) p $1051.methods
(method_array_t) $1054 = {
? list_array_tt = {
?? ? = {
? ? ? list = 0x0000000100001100
? ? ? arrayAndFlag = 4294971648
? ? }
? }
}
(lldb) p $1054.list
(method_list_t *) $1055 = 0x0000000100001100
(lldb) p $1055->get(0)
(method_t) $1056 = {
? name = "eat"
? types = 0x0000000100000f72 "v16@0:8"
? imp = 0x0000000100000bb0 (testRuntime`-[Person eat] at Person.m:14)
}
//省略其余打印
//.....
?cache
上面提到了objc_class結(jié)構(gòu)體中的cache字段,它用于緩存調(diào)用過(guò)的方法。這個(gè)字段是一個(gè)指向 cache_t 結(jié)構(gòu)體的指針,其定義如下:
struct cache_t {
?? struct bucket_t *_buckets; //緩存方法的哈希桶數(shù)組指針,桶的數(shù)量 = mask + 1
? ? mask_t _mask; //桶的數(shù)量 - 1
? ? mask_t _occupied; //已經(jīng)緩存的方法數(shù)量。
};
該結(jié)構(gòu)體的字段描述如下:
1.buckets:緩存Method指針的數(shù)組。這個(gè)數(shù)組可能包含不超過(guò)mask+1個(gè)元素。需要注意的是,當(dāng)這個(gè)指針為NULL時(shí),表示此時(shí)緩存bucket沒(méi)有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)。一個(gè)類(lèi)初始緩存中的桶的數(shù)量是4,每次桶數(shù)量擴(kuò)容時(shí)都乘2。
2.mask:一個(gè)整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過(guò)程中,Objective-C runtime 用指向方法selector的指針和這個(gè)字段進(jìn)行一個(gè) AND操作確定開(kāi)始線(xiàn)性查找數(shù)組的索引位置(index = (mask & selector)。是一個(gè)簡(jiǎn)單的hash散列算法
3.occupied:一個(gè)整數(shù),指定實(shí)際占用的緩存bucket的總數(shù)。每當(dāng)將一個(gè)方法的緩存信息保存到桶中時(shí)occupied的數(shù)量加1,如果數(shù)量到達(dá)桶容量的3/4時(shí),系統(tǒng)就會(huì)將桶的容量增大2倍變,并按照這個(gè)規(guī)則依次繼續(xù)擴(kuò)展下去。
?category
在runtime中分類(lèi)的定義如下,從定義中就可以看出,分類(lèi)可以給類(lèi)添加對(duì)象方法,類(lèi)方法,協(xié)議,以及屬性等。
struct category_t {
? ? const char *name;
? ? classref_t cls;
? ? struct method_list_t *instanceMethods; //對(duì)象方法列表
? ? struct method_list_t *classMethods; //類(lèi)方法列表
? ? struct protocol_list_t *protocols; //協(xié)議
? ? struct property_list_t *instanceProperties; //對(duì)象的屬性列表
? ? // Fields below this point are not always present on disk.
? ? struct property_list_t *_classProperties;
? ? method_list_t *methodsForMeta(bool isMeta) {
? ? ? ? if (isMeta) return classMethods;
? ? ? ? else return instanceMethods;
? ? }
? ? property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
以下是Person類(lèi)的一個(gè)分類(lèi)的聲明與實(shí)現(xiàn)。


正如文章前面打印結(jié)果顯示的,自定義類(lèi)的category中的數(shù)據(jù)在編譯時(shí)就已經(jīng)存在了類(lèi)對(duì)象的 class_ro_t 中對(duì)應(yīng)的屬性里,例如方法與本類(lèi)的方法放在同一個(gè)方法列表里等。在類(lèi)對(duì)象初始化時(shí),再存儲(chǔ)到class_rw_t中。
需要注意的一點(diǎn)是,在分類(lèi)中添加的方法與本類(lèi)的方法存儲(chǔ)在同一個(gè)方法列表,即使方法名相同,也不會(huì)替換掉本類(lèi)的方法,只是分類(lèi)的方法是放在列表中的最前。因此當(dāng)分類(lèi)中添加的方法與本類(lèi)方法同名時(shí),分類(lèi)方法會(huì)先被找到并調(diào)用,隨即結(jié)束查找,造成了一種分類(lèi)方法覆蓋了本類(lèi)方法的錯(cuò)覺(jué)。但其實(shí)本類(lèi)方法依然存在在方法列表中。
(lldb) p (objc_class *)0x100002518
(objc_class *) $0 = 0x0000000100002518
(lldb) p (class_data_bits_t *)0x0000000100002538
(class_data_bits_t *) $1 = 0x0000000100002538
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001000023a0
(lldb) p (class_ro_t *)$2
(class_ro_t *) $3 = 0x00000001000023a0
(lldb) p *$3
(class_ro_t) $4 = {
? flags = 388
? instanceStart = 8
? instanceSize = 24
? reserved = 0
? ivarLayout = 0x0000000100001eca "\x11"
? name = 0x0000000100001ec3 "Person"
? baseMethodList = 0x0000000100002188
? baseProtocols = 0x0000000000000000
? ivars = 0x0000000100002338
? weakIvarLayout = 0x0000000000000000
? baseProperties = 0x00000001000022b8
}
(lldb) p $3->baseMethodList
(method_list_t *) $5 = 0x0000000100002188
(lldb) p $5->get(0)
(method_t) $6 = {
? name = "setTitle:"?
? types = 0x0000000100001f63 "v24@0:8@16"
? imp = 0x0000000100001c00 (testRuntime`-[Person(SuperMan) setTitle:] at Person+SuperMan.m:14)
}
(lldb) p $5->get(1)
(method_t) $7 = {
? name = "title"
? types = 0x0000000100001f5b "@16@0:8"
? imp = 0x0000000100001c70 (testRuntime`-[Person(SuperMan) title] at Person+SuperMan.m:18)
}
(lldb) p $5->get(2)
(method_t) $8 = {
? name = "eat"? //分類(lèi)中添加的eat方法
? types = 0x0000000100001f53 "v16@0:8"
? imp = 0x0000000100001cd0 (testRuntime`-[Person(SuperMan) eat] at Person+SuperMan.m:23)
}
(lldb) p $5->get(3)
(method_t) $9 = {
? name = "fly"
? types = 0x0000000100001f53 "v16@0:8"
? imp = 0x0000000100001d00 (testRuntime`-[Person(SuperMan) fly] at Person+SuperMan.m:26)
}
(lldb) p $5->get(4)
(method_t) $10 = {
? name = "eat"? ? //本類(lèi)中的eat方法
? types = 0x0000000100001f53 "v16@0:8"
? imp = 0x0000000100001990 (testRuntime`-[Person eat] at Person.m:14)
}
(lldb)?
在分類(lèi)中利用 runtime 添加的屬性也是在編譯時(shí)期就已經(jīng)存在了類(lèi)對(duì)象的 class_ro_t 的baseProperties列表中,但是并不會(huì)生成對(duì)應(yīng)的成員變量ivar,在class_ro_t的ivars里找不到它。類(lèi)對(duì)象初始化完成以后,它會(huì)被存儲(chǔ)在class_rw_t的properties中。它的set和get方法會(huì)跟著分類(lèi)的其他方法一起存儲(chǔ)在類(lèi)對(duì)象的方法列表中。
(lldb) p $4.ivars
(const ivar_list_t *) $11 = 0x0000000100002338
(lldb) p $11->get(0)
(ivar_t) $12 = {
? offset = 0x00000001000024e8 //內(nèi)存偏移量
? name = 0x0000000100001f10 "_height"
? type = 0x0000000100001f81 "f"
? alignment_raw = 2
? size = 4
}
(lldb) p $11->get(1)
(ivar_t) $13 = {
? offset = 0x00000001000024e0
? name = 0x0000000100001f18 "_sex"
? type = 0x0000000100001f83 "i"
? alignment_raw = 2
? size = 4
}
(lldb) p $11->get(2)
(ivar_t) $14 = {
? offset = 0x00000001000024d8
? name = 0x0000000100001f1d "_name"
? type = 0x0000000100001f85 "@"NSString""
? alignment_raw = 3
? size = 8
}
(lldb) p $4.baseProperties
(property_list_t *) $16 = 0x00000001000022b8
(lldb) p $16->get(0)
(property_t) $17 = (name = "title", attributes = "T@"NSString",C,N")? //runtime添加的title
(lldb) p $16->get(1)
(property_t) $18 = (name = "name", attributes = "T@"NSString",&,V_name")
(lldb) p $16->get(2)
(property_t) $19 = (name = "sex", attributes = "Ti,V_sex")
(lldb) p $16->get(3)
成員變量ivar_t 結(jié)構(gòu)體中有一個(gè)offset屬性,是該成員變量對(duì)于該對(duì)象的內(nèi)存地址偏移了多少,給成員變量賦值或者取值的時(shí)候都是使用的這個(gè)地址來(lái)訪(fǎng)問(wèn)成員變量的,包括set 和 get 方法。而runtime添加的屬性不會(huì)生成對(duì)應(yīng)的成員變量。
因此runtime 提供了objc_setAssociatedObject 方法和 objc_getAssociatedObject方法 。查看這兩個(gè)方法的實(shí)現(xiàn)就能看到,runtime 中定義了 AssociationsManager 管理所有的關(guān)聯(lián)對(duì)象,它有一個(gè)靜態(tài)AssociationsHashMap來(lái)存儲(chǔ)所有的關(guān)聯(lián)對(duì)象。并且會(huì)根據(jù)指定對(duì)內(nèi)存管理策略進(jìn)行內(nèi)存管理。
簡(jiǎn)單而言就是通過(guò)AssociationsManager 確定了屬性的存儲(chǔ)地址。所以說(shuō)屬性的set 和 get 方法里調(diào)用這兩個(gè)方法是將屬性與對(duì)象進(jìn)行了綁定后,就能正常訪(fǎng)問(wèn)這個(gè)屬性了。
從前面的調(diào)試結(jié)果,已經(jīng)證明了其實(shí)分類(lèi)中添加的屬性也是在編譯時(shí)就能確定的,為什么不同時(shí)也為它生成ivar屬性和set,get方法呢?其實(shí)在runtime 初始化時(shí),有一部分分類(lèi)信息是還未被加載到本類(lèi)中的。例如在類(lèi)的初始化方法會(huì)再獲取一次未加載的分類(lèi),添加到本類(lèi)中。(經(jīng)打印發(fā)現(xiàn)讀取到的分類(lèi),都不是自定義類(lèi)相關(guān)的,而是例如NSSet,NSArray等類(lèi))。
以下為類(lèi)對(duì)象的初始化時(shí)會(huì)調(diào)用的方法
// 設(shè)置類(lèi)的方法列表、協(xié)議列表、屬性列表,包括category的方法
static void methodizeClass(Class cls)
{
? ? // 添加category方法
? ? category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
? ? attachCategories(cls, cats, false /*don't flush caches*/);
? ? // 省略無(wú)關(guān)代碼
? ? //........
? ? if (cats) free(cats);
}
而Objective-C 在編譯時(shí)期就會(huì)根據(jù)類(lèi)的ivars里的變量進(jìn)行內(nèi)存對(duì)齊,計(jì)算出instancesize,作為初始化類(lèi)的實(shí)例對(duì)象時(shí)實(shí)例對(duì)象的內(nèi)存大小。
個(gè)人推測(cè)正是因?yàn)橛幸徊糠址诸?lèi)的數(shù)據(jù)是在運(yùn)行期間才添加到本類(lèi)中的, 但此時(shí)類(lèi)對(duì)象內(nèi)存的分布已經(jīng)確定,若此時(shí)再添加成員變量則會(huì)改變內(nèi)存的分布情況,這在編譯性語(yǔ)言中是不允許的。所以不允許分類(lèi)改變本類(lèi)的內(nèi)存分布情況,即聲明的屬性是通過(guò)AssociationsManager 管理管理,而不會(huì)生成成員變量。反觀擴(kuò)展(extension),它是為一個(gè)已知的類(lèi)添加一些私有的信息,都是寫(xiě)在本類(lèi)的.m文件中,所以必須有這個(gè)類(lèi)的源碼,才能寫(xiě)擴(kuò)展。它是在編譯時(shí)期生效的,所以能直接為類(lèi)添加成員變量。
?Runtime消息傳遞
編譯型語(yǔ)言有三種基礎(chǔ)的函數(shù)派發(fā)方式: 直接派發(fā)(Direct Dispatch)**, **函數(shù)表派發(fā)(Table Dispatch)和消息機(jī)制派發(fā)(Message Dispatch)。 大多數(shù)語(yǔ)言都會(huì)支持一到兩種, Java 默認(rèn)使用函數(shù)表派發(fā), 也可以使用直接派發(fā). C++ 默認(rèn)使用直接派發(fā), 但可以通過(guò)修飾符改成函數(shù)表派發(fā). 而 Objective-C 則總是使用消息機(jī)制派發(fā), 但是也允許開(kāi)發(fā)者使用 C 直接派發(fā)來(lái)提高性能。swift則是直接派發(fā)和函數(shù)表派發(fā)為主,objc顯示或者隱式修飾的方法則使用消息派發(fā)機(jī)制。
在 Objective-C 中,編譯時(shí)期并不能決定最終執(zhí)行時(shí)調(diào)用哪個(gè)函數(shù)(Objective-C 中函數(shù)調(diào)用稱(chēng)為消息傳遞),而是在運(yùn)行時(shí)才能最終確定。Objective-C 的這種動(dòng)態(tài)綁定機(jī)制正是通過(guò) Runtime 庫(kù)實(shí)現(xiàn)的。以下就介紹一下在Objective-C中調(diào)用方法后,消息傳遞的過(guò)程。
?Method(objc_method)
Objectiv-C中方法的定義如下
//objc-runtime-new.h
struct method_t {
? ? SEL name;?
? ? const char *types;
? ? MethodListIMP imp;?
? ? ......
}
SEL叫方法選擇器,簡(jiǎn)單而言就是方法的名字,Objective-C在編譯時(shí),會(huì)依據(jù)每一個(gè)方法的名字、參數(shù)序列,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類(lèi)型的地址),這個(gè)標(biāo)識(shí)就是SEL。IMP實(shí)際上是一個(gè)函數(shù)指針,指向方法實(shí)現(xiàn)的首地址。
在 Objective-C 里面調(diào)用一個(gè)方法的時(shí)候,Runtime 庫(kù)會(huì)將這個(gè)調(diào)用翻譯成 objc_msgSend 形式進(jìn)行調(diào)用,它定義在 Objc-msg-arm.s 中,是所有OC方法調(diào)用的核心引擎。在這個(gè)方法里會(huì)去查找真正需要調(diào)用的方法,并執(zhí)行該方法。為了追求高效率,objc_msgSend 函數(shù)的內(nèi)部代碼實(shí)現(xiàn)是用匯編語(yǔ)言。
objc_msgSend的聲明如下
objc_msgSend(id self, SEL op, ...)
該方法有兩個(gè)參數(shù) self 和 op,分別是方法的調(diào)用者與方法選擇器。它們也會(huì)作為參數(shù)傳遞給每個(gè)方法實(shí)現(xiàn)。ps:我們可以在每個(gè)方法實(shí)現(xiàn)中使用self,就是因?yàn)閌self`作為隱式參數(shù)傳遞進(jìn)來(lái)了。
objc_msgSend 的執(zhí)行的流程是這樣的:
1.判斷方法調(diào)用者(self)否為nil,如果是nil則函數(shù)直接返回。因此在OC中用一個(gè)nil對(duì)象調(diào)用方法時(shí),不會(huì)崩潰也不會(huì)執(zhí)行被調(diào)用的方法。
2.獲取調(diào)用者的isa,查找類(lèi)對(duì)象/meta-class。
3.在2中查找到的類(lèi)對(duì)象/meta-class中的cache中查找方法實(shí)現(xiàn)。用傳入的SEL類(lèi)型的op的指針地址和mask進(jìn)行一個(gè)哈希運(yùn)算,獲得一個(gè)索引。從bucket_t *中取出這個(gè)索引對(duì)應(yīng)的bucket_t結(jié)構(gòu)體,比照這個(gè)結(jié)構(gòu)體中的key與sel是否相等。相等的話(huà)就表示命中緩存,則調(diào)用方法實(shí)現(xiàn)并結(jié)束查找。
4.如果在緩存列表中沒(méi)有找到對(duì)應(yīng)的方法,則在方法列表中查到,找到后在cache中緩存一份,并調(diào)用。如果方法列表中也沒(méi)有找到,則通過(guò)superclass指針找到父類(lèi),在父類(lèi)中進(jìn)行同樣的查找,找到后調(diào)用方法實(shí)現(xiàn)并將其存儲(chǔ)到子類(lèi)的cache中。
5.如果在步驟4中,一直追溯到了基類(lèi)依然沒(méi)有找到方法的話(huà),進(jìn)入方法決議以及消息轉(zhuǎn)發(fā)。
6.如果最終都找不到方法,則報(bào)錯(cuò),拋出異常。
### 方法決議(method resolve)
在objc_msgSend的執(zhí)行過(guò)程中,如果在方法調(diào)用者的所屬的繼承體系中無(wú)法找到方法的話(huà),就會(huì)進(jìn)入方法決議階段。
比如以下這兩種情況,編譯都不會(huì)報(bào)錯(cuò),但是運(yùn)行會(huì)奔潰,因?yàn)閜erson對(duì)象實(shí)際是Animal類(lèi)型的,它并沒(méi)有實(shí)現(xiàn)eat方法

可以在類(lèi)中實(shí)現(xiàn)下列方法,作為當(dāng)對(duì)象無(wú)法響應(yīng)方法時(shí)的處理。如果不實(shí)現(xiàn)該方法的話(huà),方法決議的默認(rèn)返回值為NO,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段
//類(lèi)方法的方法決議
+ (BOOL)resolveClassMethod:(SEL)sel {
? ? return NO;
}
//實(shí)例方法的方法決議
+ (BOOL)resolveInstanceMethod:(SEL)sel {
? ? return NO;
}
//例子
@implementation ViewController
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? [self performSelector:@selector(sayHello) withObject:nil];
}
+ (BOOL)resolveInstanceMethod: (SEL) sel {
? ? if (sel == @selector(sayHello)) {
? ? ? ? Class cls = [self class];
? ? ? ? BOOL isAdd =? class_addMethod(cls, sel, (IMP)test, "v@:");
? ? ? ? if (isAdd){
? ? ? ? ? ? return YES;
? ? ? ? } else {
? ? ? ? ? ? return [super resolveInstanceMethod:sel];
? ? ? ? }
? ? }
?? return [super resolveInstanceMethod:sel];
}
void test(id obj,SEL _cmd){
? ? NSLog(@"調(diào)用了test方法");
}
@end
?消息轉(zhuǎn)發(fā)
如果方法的調(diào)用者沒(méi)有重寫(xiě)上述的方法,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段,嘗試找到一個(gè)能響應(yīng)該消息的對(duì)象。如果獲取到,則直接轉(zhuǎn)發(fā)給它。如果返回了 nil,繼續(xù)下面的動(dòng)作。消息轉(zhuǎn)發(fā)的方法定義和執(zhí)行步驟如下:
//1.可以在這個(gè)用法里提供一個(gè)新的對(duì)象作為方法的接收者,從新對(duì)象開(kāi)始重新執(zhí)行查找方法實(shí)現(xiàn)的流程,找到了也同樣會(huì)在 object 的類(lèi)對(duì)象的 _buckets 里緩存起來(lái)。如果這個(gè)方法返回nil的話(huà) 則進(jìn)入下一步(方法默認(rèn)返回nil)
這一步合適于我們將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上。在這一步無(wú)法對(duì)消息進(jìn)行進(jìn)一步的處理,如操作消息的參數(shù)和返回值。
+ (id)forwardingTargetForSelector:(SEL)sel { //這個(gè)方法現(xiàn)在在編譯器里調(diào)用已經(jīng)沒(méi)有提示了
? ? return nil;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
? ? return nil;
}
//2.嘗試獲得一個(gè)方法簽名。如果獲取不到,則直接調(diào)用 doesNotRecognizeSelector 拋出異常。
//如果獲取到了,則將簽名作為參數(shù)傳給下個(gè)方法。這條消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector,目標(biāo)(target)和參數(shù)。不手動(dòng)實(shí)現(xiàn)這個(gè)方法,系統(tǒng)也會(huì)嘗試獲取簽名傳遞給下個(gè)方法
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
? ? _objc_fatal("+[NSObject methodSignatureForSelector:] "
? ? ? ? ? ? ? ? "not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
? ? _objc_fatal("-[NSObject methodSignatureForSelector:] "
? ? ? ? ? ? ? ? "not available without CoreFoundation");
}
//3.從獲取到的方法簽名中,可以拿到sel,然后可以在這個(gè)方法中做相應(yīng)的處理
+ (void)forwardInvocation:(NSInvocation *)invocation {
? ? [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
? ? [self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
//例子
- (void)forwardInvocation:(NSInvocation *)anInvocation {
? ? SEL sel = anInvocation.selector;
? ? Person *person = [Person new];
? ? if([p respondsToSelector:sel]) {
? ? ? ? [anInvocation invokeWithTarget:person];
? ? } else {
? ? ? ? [self doesNotRecognizeSelector:sel];? //拋出異常
? ? }
}
至此方法的調(diào)用就全部結(jié)束了。如果以上方法都沒(méi)有實(shí)現(xiàn),那么程序就會(huì)拋出異常,提示找不到方法實(shí)現(xiàn)。
總結(jié)
本文只是介紹了Runtime中類(lèi)對(duì)象的數(shù)據(jù)結(jié)構(gòu)和初始化過(guò)程中的一些細(xì)節(jié),以及消息傳遞和轉(zhuǎn)發(fā)的基本機(jī)制。Runtime在oc中運(yùn)用其實(shí)非常廣泛,例如KVO鍵值監(jiān)聽(tīng),Method Swizzling方法交換,動(dòng)態(tài)創(chuàng)建類(lèi),等等知識(shí)值得深入的了解和探索,介于篇幅問(wèn)題在本文就不一一展開(kāi)了。
參考鏈接:
http://www.itdecent.cn/p/91bfe3f11eec 深入理解 Swift 派發(fā)機(jī)制
https://juejin.im/post/5b70ec3351882560fc512fc4 深入解構(gòu)objc_msgSend函數(shù)的實(shí)現(xiàn)
https://tech.meituan.com/2015/08/12/deep-understanding-object-c-of-method-caching.html 深入理解 Objective-C:方法緩存
http://www.itdecent.cn/p/6ebda3cd8052 iOS Runtime詳解