注:本文始發(fā)于個(gè)人 GitHub 項(xiàng)目 ShannonChenCHN/iOSDevLevelingUp。
關(guān)于 objc4 源碼的一些說明:
- objc4 的源碼不能直接編譯,需要配置相關(guān)環(huán)境才能運(yùn)行??梢栽?a target="_blank">這里下載可調(diào)式的源碼。
- objc 運(yùn)行時(shí)源碼的入口在
void _objc_init(void)函數(shù)。
目錄
- 1.Objective-C 對(duì)象是什么?Class 是什么?id 又是什么?
- 2.isa 是什么?為什么要有 isa?
- 3.為什么在 Objective-C 中,所以的對(duì)象都用一個(gè)指針來追蹤?
- 4.Objective-C 對(duì)象是如何被創(chuàng)建(alloc)和初始化(init)的?
- 5.Objective-C 對(duì)象的實(shí)例變量是什么?為什么不能給 Objective-C 對(duì)象動(dòng)態(tài)添加實(shí)例變量?
- 6.Objective-C 對(duì)象的屬性是什么?屬性跟實(shí)例變量的區(qū)別?
- 7.Objective-C 對(duì)象的方法是什么?Objective-C 對(duì)象的方法在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)是什么樣的?
- 8.什么是 IMP?什么是選擇器 selector ?
- 9.消息發(fā)送和消息轉(zhuǎn)發(fā)
- 10.Method Swizzling
- 11.Category
- 12.Associated Objects 的原理是什么?到底能不能在 Category 中給 Objective-C 類添加屬性和實(shí)例變量?
- 13.Objective-C 中的 Protocol 是什么?
- 14.
self和super的本質(zhì) - 15.
load方法和initialize方法
1. Objective-C 對(duì)象是什么?Class 是什么?id 又是什么?
所有的類都繼承 NSObject 或者 NSProxy,先來看看這兩個(gè)類在各自的公開頭文件中的定義:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
@interface NSProxy <NSObject> {
Class isa;
}
在 objc.h 文件中,對(duì)于 Class,id 以及 objc_object 的定義:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
runtime.h 文件中對(duì) objc_class 的定義:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
在 Objective-C 中,每一個(gè)對(duì)象是一個(gè)結(jié)構(gòu)體,每個(gè)對(duì)象都有一個(gè) isa 指針,類對(duì)象 Class 也是一個(gè)對(duì)象。所以,我們說,凡是包含 isa 指針的,都可以被認(rèn)為是 Objective-C 中的對(duì)象。運(yùn)行時(shí)可以通過 isa 指針,查找到該對(duì)象是屬于什么類(Class)。
2. isa 是什么?為什么要有 isa?
在 Runtime 源碼中,對(duì)于 objc_object 和 objc_class 的定義分別如下:
struct objc_object {
private:
isa_t isa; // isa 是一個(gè) union 聯(lián)合體,其包含這個(gè)對(duì)象所屬類的信息
public:
Class ISA(); // ISA() assumes this is NOT a tagged pointer object
Class getIsa(); // getIsa() allows this to be a tagged pointer object
...
};
struct objc_class : objc_object {
// 這里沒寫 isa,其實(shí)繼承了 objc_object 的 isa , 在這里 isa 是一個(gè)指向元類的指針
// Class ISA;
Class superclass; // 指向當(dāng)前類的父類
cache_t cache; // formerly cache pointer and vtable
// 用于緩存指針和 vtable,加速方法的調(diào)用
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// 相當(dāng)于 class_rw_t 指針加上 rr/alloc 的標(biāo)志
// bits 用于存儲(chǔ)類的方法、屬性、遵循的協(xié)議等信息的地方
// 針對(duì) class_data_bits_t 的 data() 函數(shù)的封裝,最終返回一個(gè) class_rw_t 類型的結(jié)構(gòu)體變量
// Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中
class_rw_t *data() {
return bits.data();
}
...
};
objc_class 繼承于 objc_object,所以 objc_class 也是一個(gè) objc_object,objc_object 和 objc_class 都有一個(gè)成員變量 isa。isa 變量的類型是 isa_t,這個(gè) isa_t 其實(shí)是一個(gè)聯(lián)合體(union),其中包括成員量 cls。也就是說,每個(gè) objc_object 通過自己持有的 isa,都可以查找到自己所屬的類,對(duì)于 objc_class 來說,就是通過 isa 找到自己所屬的元類(meta class)。
#define ISA_MASK 0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK 0x001f800000000001ULL
#define ISA_MAGIC_VALUE 0x001d800000000001ULL
#define RC_ONE (1ULL<<56)
#define RC_HALF (1ULL<<7)
// isa 的類型是一個(gè) isa_t 聯(lián)合體
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls; // 所屬的類
uintptr_t bits;
struct {
uintptr_t nonpointer : 1; // 表示 isa_t 的類型,0 表示 raw isa,也就是沒有結(jié)構(gòu)體的部分,訪問對(duì)象的 isa 會(huì)直接返回一個(gè)指向 cls 的指針,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時(shí) isa 的類型。1 表示當(dāng)前 isa 不是指針,但是其中也有 cls 的信息,只是其中關(guān)于類的指針都是保存在 shiftcls 中。
uintptr_t has_assoc : 1; // 對(duì)象含有或者曾經(jīng)含有關(guān)聯(lián)引用,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
uintptr_t has_cxx_dtor : 1; // 表示當(dāng)前對(duì)象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒有析構(gòu)器就會(huì)快速釋放內(nèi)存。
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6; // 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
uintptr_t weakly_referenced : 1; // 對(duì)象被指向或者曾經(jīng)指向一個(gè) ARC 的弱變量,沒有弱引用的對(duì)象可以更快釋放
uintptr_t deallocating : 1; // 對(duì)象正在釋放內(nèi)存
uintptr_t has_sidetable_rc : 1; // 對(duì)象的引用計(jì)數(shù)太大了,存不下
uintptr_t extra_rc : 8; // 對(duì)象的引用計(jì)數(shù)超過 1,會(huì)存在這個(gè)這個(gè)里面,如果引用計(jì)數(shù)為 10,extra_rc 的值就為 9
};
};
而在 Objective-C 中,對(duì)象的方法都是存儲(chǔ)在類中,而不是對(duì)象中(如果每一個(gè)對(duì)象都保存了自己能執(zhí)行的方法,那么對(duì)內(nèi)存的占用有極大的影響)。
// Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中
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; // 一個(gè)指向常量的指針,其中存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 所遵循的協(xié)議的列表
Class firstSubclass;
Class nextSiblingClass;
...
};
當(dāng)一個(gè)對(duì)象的實(shí)例方法被調(diào)用時(shí),它要通過自己持有的 isa 來查找對(duì)應(yīng)的類,然后在這里的 class_data_bits_t 結(jié)構(gòu)體中查找對(duì)應(yīng)方法的實(shí)現(xiàn)(每個(gè)對(duì)象可以通過 cls->data()-> methods 來訪問所屬類的方法)。同時(shí),每一個(gè) objc_class 也有一個(gè)指向自己的父類的指針 super_class 用來查找繼承的方法。
因?yàn)樵?Objective-C 中,類其實(shí)也是一個(gè)對(duì)象,每個(gè)類也有一個(gè) isa 指向自己所屬的元類。所以無論是類還是對(duì)象都能通過相同的機(jī)制查找方法的實(shí)現(xiàn)。
isa 在方法調(diào)用時(shí)扮演的角色:
- 調(diào)用一個(gè)對(duì)象的實(shí)例方法時(shí),通過對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn)
- 調(diào)用一個(gè)類的類方法時(shí),通過類的 isa 在元類中獲取方法的實(shí)現(xiàn)
- 如果在當(dāng)前類/元類中沒找到,就會(huì)通過類/元類的 superclass 在繼承鏈中一級(jí)一級(jí)往上查找

<div align='center'>圖 1. 對(duì)象,類與元類之間的關(guān)系</div>
isa_t 中包含什么:
isa 的類型 isa_t 是一個(gè) union 類型的結(jié)構(gòu)體,也就是說其中的 isa_t、cls、 bits 還有結(jié)構(gòu)體共用同一塊地址空間,而 isa 總共會(huì)占據(jù) 64 位的內(nèi)存空間(決定于其中的結(jié)構(gòu)體)。其中包含的信息見上面的代碼注釋。
現(xiàn)在直接訪問對(duì)象(objc_object)的 isa 已經(jīng)不會(huì)返回類指針了,取而代之的是使用 ISA() 方法來獲取類指針。其中 ISA_MASK 是宏定義,這里通過掩碼的方式獲取類指針。
結(jié)論
(1)isa 的作用:用于查找對(duì)象(或類對(duì)象)所屬類(或元類)的信息,比如方法列表。
(2)isa 是什么:isa 的數(shù)據(jù)結(jié)構(gòu)是一個(gè) isa_t 聯(lián)合體,其中包含其所屬的 Class 的地址,通過訪問對(duì)象的 isa,就可以獲取到指向其所屬 Class 的指針(針對(duì) tagged pointer 的情況,也就是 non-pointer isa,有點(diǎn)不一樣的是,除了指向 class 的指針,isa 中還會(huì)包含對(duì)象本身的一些信息,比如對(duì)象是否被弱引用)。
3. 為什么在 Objective-C 中,所以的對(duì)象都用一個(gè)指針來追蹤?
內(nèi)存中的數(shù)據(jù)類型分為兩種:值類型和引用類型。指針就是引用類型,struct 類型就是值類型。
值類型在傳值時(shí)需要拷貝內(nèi)容本身,而引用類型在傳遞時(shí),拷貝的是對(duì)象的地址。所以,一方面,值類型的傳遞占用更多的內(nèi)存空間,使用引用類型更節(jié)省內(nèi)存開銷;另一方面,也是最主要的原因,很多時(shí)候,我們需要把一個(gè)對(duì)象交給另一個(gè)函數(shù)或者方法去修改其中的內(nèi)容(比如說一個(gè) Person 對(duì)象的 age 屬性),顯然如果我們想讓修改方獲取到這個(gè)對(duì)象,我們需要的傳遞的是地址,而不是復(fù)制一份。
對(duì)于像 int 這樣的基本數(shù)據(jù)類型,拷貝起來更快,而且數(shù)據(jù)簡(jiǎn)單,方便修改,所以就不用指針了。
另一方面,對(duì)象的內(nèi)存是分配在堆上的,而值類型是分配到棧上的。所以一般對(duì)象的生命周期會(huì)比普通的值類型要長(zhǎng),而且創(chuàng)建和銷毀對(duì)象以及內(nèi)存管理是要消耗性能的,所以通過指針來引用一個(gè)對(duì)象,比直接復(fù)制和創(chuàng)建對(duì)象要更有效率、更節(jié)省性能。
參考:
- Understanding pointers?
- need of pointer objects in objective c
- Why "Everything" in Objective C is pointers. I mean why I should declare NSArray instance variables in pointers.
- Why do all objects in Objective-C have to use pointers?
4. Objective-C 對(duì)象是如何被創(chuàng)建(alloc)和初始化(init)的?
整個(gè)對(duì)象的創(chuàng)建過程其實(shí)就做了兩件事情:為對(duì)象分配內(nèi)存空間,以及初始化 isa(一個(gè)聯(lián)合體)。
(1)創(chuàng)建 NSObject 對(duì)象的過程
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
最核心的邏輯就在 _class_createInstanceFromZone 函數(shù)中:
static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {
// 實(shí)例變量?jī)?nèi)存大小,實(shí)例大小 instanceSize 會(huì)存儲(chǔ)在類的 isa_t 結(jié)構(gòu)體中,經(jīng)過對(duì)齊最后返回
size_t size = cls->instanceSize(extraBytes);
// 給對(duì)象申請(qǐng)內(nèi)存空間
id obj = (id)calloc(1, size);
if (!obj) return nil;
// 初始化 isa
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
獲取對(duì)象內(nèi)存空間大小:
size_t instanceSize(size_t extraBytes) {
// Core Foundation 需要所有的對(duì)象的大小至少有 16 字節(jié)。
size_t size = alignedInstanceSize() + extraBytes;
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
初始化 isa,isa 是一個(gè) isa_t 聯(lián)合體:
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) {
if (!indexed) {
isa.cls = cls;
} else {
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
(2)NSObject 對(duì)象初始化的過程
NSObject 對(duì)象的初始化實(shí)際上就是返回 +alloc 執(zhí)行后得到的對(duì)象本身:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj) {
return obj;
}
5. Objective-C 對(duì)象的實(shí)例變量是什么?為什么不能給 Objective-C 對(duì)象動(dòng)態(tài)添加實(shí)例變量?
(1)兩個(gè)注意點(diǎn):
- Objective-C 的
->操作符不是C語言指針操作 - Objective-C 對(duì)象不能簡(jiǎn)單對(duì)應(yīng)于一個(gè) C struct,訪問成員變量不等于訪問 C struct 成員
(2)Non Fragile ivars
在 Runtime 的現(xiàn)行版本中,最大的特點(diǎn)就是健壯的實(shí)例變量。
當(dāng)一個(gè)類被編譯時(shí),實(shí)例變量的布局也就形成了,它表明訪問類的實(shí)例變量的位置。用舊版OSX SDK 編譯的 MyObject 類成員變量布局是這樣的,MyObject的成員變量依次排列在基類NSObject 的成員后面。
當(dāng)蘋果發(fā)布新版本OSX SDK后,NSObject增加了兩個(gè)成員變量。如果沒有Non Fragile ivars特性,我們的代碼將無法正常運(yùn)行,因?yàn)镸yObject類成員變量布局在編譯時(shí)已經(jīng)確定,有兩個(gè)成員變量和基類的內(nèi)存區(qū)域重疊了。此時(shí),我們只能重新編譯MyObject代碼,程序才能在新版本系統(tǒng)上運(yùn)行。
現(xiàn)在有了 Non Fragile ivars 之后,問題就解決了。在程序啟動(dòng)后,runtime加載MyObject類的時(shí)候,通過計(jì)算基類的大小,runtime 動(dòng)態(tài)調(diào)整了 MyObject 類成員變量布局,把MyObject成員變量的位置向后移動(dòng)8個(gè)字節(jié)。于是我們的程序無需編譯,就能在新版本系統(tǒng)上運(yùn)行。
(3)Non Fragile ivars 是如何實(shí)現(xiàn)的呢?
當(dāng)成員變量布局調(diào)整后,靜態(tài)編譯的native程序怎么能找到變量的新偏移位置呢?
根據(jù) Runtime 源碼可知,一個(gè)變量實(shí)際上就是一個(gè) ivar_t 結(jié)構(gòu)體。而每個(gè) Objective-C 對(duì)象對(duì)應(yīng)于 struct objc_object,后者的 isa 指向類定義,即 struct objc_class:
typedef struct ivar_t *Ivar;
struct objc_object {
private:
isa_t isa;
//...
};
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
class_rw_t *data() {
return bits.data();
}
//...
};
沿著 objc_class 的 data()->ro->ivars 找下去,struct ivar_list_t是類所有成員變量的定義列表。通過 first 變量,可以取得類里任意一個(gè)類成員變量的定義。
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; // 一個(gè)指向常量的指針,其中存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議
//...
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // 實(shí)例變量大小,決定對(duì)象創(chuàng)建時(shí)要分配的內(nèi)存
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 類名
method_list_t * baseMethodList; // (編譯時(shí)確定的)方法列表
protocol_list_t * baseProtocols; // (編譯時(shí)確定的)所屬協(xié)議列表
const ivar_list_t * ivars; // (編譯時(shí)確定的)實(shí)例變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; // (編譯時(shí)確定的)屬性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
struct ivar_list_t {
uint32_t entsize;
uint32_t count;
ivar_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
//...
};
這里的 offset,應(yīng)該就是用來記錄這個(gè)成員變量在對(duì)象中的偏移位置。也就是說,runtime在發(fā)現(xiàn)基類大小變化時(shí),通過修改offset,來更新子類成員變量的偏移值。
假如我們現(xiàn)在有一個(gè)繼承于 NSError 的類 MyClass,在編譯時(shí),LLVM計(jì)算出基類 NSError 對(duì)象的大小為40字節(jié),然后記錄在MyClass的類定義中,如下是對(duì)應(yīng)的C代碼。在編譯后的可執(zhí)行程序中,寫死了“40”這個(gè)魔術(shù)數(shù)字,記錄了在此次編譯時(shí)MyClass基類的大小。
class_ro_t class_ro_MyClass = {
.instanceStart = 40,
.instanceSize = 48,
//...
}
現(xiàn)在假如蘋果發(fā)布了OSX 11 SDK,NSError類大小增加到48字節(jié)。當(dāng)我們的程序啟動(dòng)后,runtime加載MyClass類定義的時(shí)候,發(fā)現(xiàn)基類的真實(shí)大小和MyClass的instanceStart不相符,得知基類的大小發(fā)生了改變。于是runtime遍歷MyClass的所有成員變量定義,將offset指向的值增加8。具體的實(shí)現(xiàn)代碼在runtime/objc-runtime-new.mm的 moveIvars()函數(shù)中。
并且,MyClass類定義的instanceSize也要增加8。這樣runtime在創(chuàng)建MyClass對(duì)象的時(shí)候,能分配出正確大小的內(nèi)存塊。
static void moveIvars(class_ro_t *ro, uint32_t superSize)
{
uint32_t diff;
diff = superSize - ro->instanceStart;
if (ro->ivars) {
// Find maximum alignment in this class's ivars
uint32_t maxAlignment = 1;
for (const auto& ivar : *ro->ivars) {
if (!ivar.offset) continue; // anonymous bitfield
uint32_t alignment = ivar.alignment();
if (alignment > maxAlignment) maxAlignment = alignment;
}
// Compute a slide value that preserves that alignment
uint32_t alignMask = maxAlignment - 1;
diff = (diff + alignMask) & ~alignMask;
// Slide all of this class's ivars en masse
for (const auto& ivar : *ro->ivars) {
if (!ivar.offset) continue; // anonymous bitfield
uint32_t oldOffset = (uint32_t)*ivar.offset;
uint32_t newOffset = oldOffset + diff;
*ivar.offset = newOffset;
if (PrintIvars) {
_objc_inform("IVARS: offset %u -> %u for %s "
"(size %u, align %u)",
oldOffset, newOffset, ivar.name,
ivar.size, ivar.alignment());
}
}
}
*(uint32_t *)&ro->instanceStart += diff;
*(uint32_t *)&ro->instanceSize += diff;
}
(4)為什么Objective-C類不能動(dòng)態(tài)添加成員變量
runtime 提供了一個(gè) class_addIvar() 函數(shù)用于給類添加成員變量,但是根據(jù)文檔中的注釋,這個(gè)函數(shù)只能在“構(gòu)建一個(gè)類的過程中”調(diào)用。一旦完成類定義,就不能再添加成員變量了。經(jīng)過編譯的類在程序啟動(dòng)后就被runtime加載,沒有機(jī)會(huì)調(diào)用addIvar。程序在運(yùn)行時(shí)動(dòng)態(tài)構(gòu)建的類需要在調(diào)用 objc_allocateClassPair 之后,調(diào)用 objc_registerClassPair 之前才可以添加成員變量。
這樣做會(huì)帶來嚴(yán)重問題,為基類動(dòng)態(tài)增加成員變量會(huì)導(dǎo)致所有已創(chuàng)建出的子類實(shí)例都無法使用。那為什么runtime允許動(dòng)態(tài)添加方法和屬性,而不會(huì)引發(fā)問題呢?
因?yàn)榉椒ê蛯傩圆⒉弧皩儆凇睂?shí)例,而成員變量“屬于”實(shí)例。我們所說的“類實(shí)例”概念,指的是一塊內(nèi)存區(qū)域,包含了isa指針和所有的成員變量。所以假如允許動(dòng)態(tài)修改類成員變量布局,已經(jīng)創(chuàng)建出的實(shí)例就不符合類定義了,變成了無效對(duì)象。但方法定義是在objc_class中管理的,不管如何增刪類方法,都不影響實(shí)例的內(nèi)存布局,已經(jīng)創(chuàng)建出的實(shí)例仍然可正常使用。
美團(tuán)的技術(shù)博客中給出的解釋比較簡(jiǎn)單,其中“破壞破壞類的內(nèi)部布局”這句話本身也是有些問題的:
extension在編譯期決議,它就是類的一部分,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡。extension一般用來隱藏類的私有信息,你必須有一個(gè)類的源碼才能為一個(gè)類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension。
但是category則完全不一樣,它是在運(yùn)行期決議的。
就category和extension的區(qū)別來看,我們可以推導(dǎo)出一個(gè)明顯的事實(shí),extension可以添加實(shí)例變量,而category是無法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期,對(duì)象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會(huì)破壞類的內(nèi)部布局,這對(duì)編譯型語言來說是災(zāi)難性的)。
參考:
6. Objective-C 對(duì)象的屬性是什么?屬性跟實(shí)例變量的區(qū)別?
屬性是一個(gè)結(jié)構(gòu)體,其中包含屬性名和屬性本身的屬性(attributes)。
我們一般是通過使用 @property 進(jìn)行屬性定義,編譯時(shí)編譯器會(huì)自動(dòng)生成對(duì)應(yīng)的實(shí)例變量(默認(rèn)情況下生成的實(shí)例變量名是在對(duì)應(yīng)的屬性名前加了下劃線“_”),同時(shí)還會(huì)自動(dòng)合成對(duì)應(yīng)的 setter 和 getter 方法用于存取屬性值。
我們可以驗(yàn)證一下,先定義一個(gè)帶有屬性的類 NyanCat,如下:
@interface NyanCat : NSObject {
int age;
NSString *name;
}
@property (nonatomic, copy) NSString *cost;
@end
@implementation NyanCat
@end
然后再通過 clang -rewrite-objc NyanCat.m 將該類重寫為 cpp 代碼后,得到了下面這些內(nèi)容:
#ifndef _REWRITER_typedef_NyanCat
#define _REWRITER_typedef_NyanCat
typedef struct objc_object NyanCat; // NyanCat 類實(shí)際上就是一個(gè) objc_object 結(jié)構(gòu)體
typedef struct {} _objc_exc_NyanCat;
#endif
extern "C" unsigned long OBJC_IVAR_$_NyanCat$_cost;
struct NyanCat_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int age;
NSString *name;
NSString *_cost;
};
//...
// 屬性 cost 的 setter 和 getter 對(duì)應(yīng)的函數(shù)
static NSString * _I_NyanCat_cost(NyanCat * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_NyanCat$_cost)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_NyanCat_setCost_(NyanCat * self, SEL _cmd, NSString *cost) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NyanCat, _cost), (id)cost, 0, 1); }
// 屬性的數(shù)據(jù)結(jié)構(gòu)
struct _prop_t {
const char *name;
const char *attributes;
};
extern "C" unsigned long int OBJC_IVAR_$_NyanCat$age __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct NyanCat, age);
extern "C" unsigned long int OBJC_IVAR_$_NyanCat$name __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct NyanCat, name);
extern "C" unsigned long int OBJC_IVAR_$_NyanCat$_cost __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct NyanCat, _cost);
// 實(shí)例變量列表
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[3];
} _OBJC_$_INSTANCE_VARIABLES_NyanCat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
3,
{{(unsigned long int *)&OBJC_IVAR_$_NyanCat$age, "age", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_NyanCat$name, "name", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_NyanCat$_cost, "_cost", "@\"NSString\"", 3, 8}}
};
// 實(shí)例方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_INSTANCE_METHODS_NyanCat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{(struct objc_selector *)"cost", "@16@0:8", (void *)_I_NyanCat_cost},
{(struct objc_selector *)"setCost:", "v24@0:8@16", (void *)_I_NyanCat_setCost_}}
};
// 屬性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_NyanCat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"cost","T@\"NSString\",C,N,V_cost"}}
};
從上面 clang 重寫的代碼中可以看到:
- 屬性列表的數(shù)據(jù)結(jié)構(gòu) _prop_list_t 中有屬性
cost對(duì)應(yīng)的屬性名和屬性的 attributes(attributes 字符串所代表的含義可以在官方文檔上查閱到)。 - 實(shí)例變量列表
_method_list_t中也有屬性cost對(duì)應(yīng)的變量信息,變量名為_cost,類型為@"NSString"。 - 實(shí)例方法列表
_method_list_t中有屬性cost對(duì)應(yīng)的 setter 和 getter 方法,這兩個(gè)方法的實(shí)現(xiàn)分別對(duì)應(yīng)的是兩個(gè)函數(shù)——_I_NyanCat_setCost_(NyanCat * self, SEL _cmd, NSString *cost)和_I_NyanCat_cost(NyanCat * self, SEL _cmd)。
以上三條結(jié)果正好驗(yàn)證了我們一開始提出的結(jié)論。
實(shí)際上,在 runtime 源碼 objc-runtime-new.h 的實(shí)現(xiàn)中,屬性就是一個(gè) property_t 類型的結(jié)構(gòu)體,其中包含屬性名以及屬性自己的屬性(attributes)。
typedef struct property_t *objc_property_t;
// 屬性的數(shù)據(jù)結(jié)構(gòu)
struct property_t {
const char *name; // property 的名字
const char *attributes; // property 的屬性
};
在實(shí)際使用 runtime 時(shí),通過下面兩個(gè)函數(shù)分別可以獲取 property 名字和 attributes 字符串。
// Returns the name of a property.
const char * _Nonnull property_getName(objc_property_t _Nonnull property);
// Returns the attribute string of a property.
const char * _Nullable property_getAttributes(objc_property_t _Nonnull property);
小結(jié):
- 一個(gè)對(duì)象的屬性實(shí)際上包括實(shí)例變量以及存取屬性值(實(shí)際上就是實(shí)例變量值)的 setter/getter 方法兩部分(這里只討論類本身定義的屬性,category 中的 @property 并沒有為我們生成實(shí)例變量以及存取方法,而需要我們手動(dòng)實(shí)現(xiàn))。
- 屬性的實(shí)際結(jié)構(gòu)是一個(gè)結(jié)構(gòu)體,其中包含屬性名和 attributes 兩部分。
7. Objective-C 對(duì)象的方法是什么?Objective-C 對(duì)象的方法在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)是什么樣的?
objc_class 有一個(gè) class_data_bits_t 類型的變量 bits,Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中,通過調(diào)用 objc_class 的 class_rw_t *data() 方法,可以獲取這個(gè) class_rw_t 類型的變量。
// Objective-C 類是一個(gè)結(jié)構(gòu)體,繼承于 objc_object
struct objc_class : objc_object {
// 這里沒寫 isa,其實(shí)繼承了 objc_object 的 isa , 在這里 isa 是一個(gè)指向元類的指針
// Class ISA;
Class superclass; // 指向當(dāng)前類的父類
cache_t cache; // formerly cache pointer and vtable
// 用于緩存指針和 vtable,加速方法的調(diào)用
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// 相當(dāng)于 class_rw_t 指針加上 rr/alloc 的標(biāo)志
// bits 用于存儲(chǔ)類的方法、屬性、遵循的協(xié)議等信息的地方
// 針對(duì) class_data_bits_t 的 data() 函數(shù)的封裝,最終返回一個(gè) class_rw_t 類型的結(jié)構(gòu)體變量
// Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中
class_rw_t *data() {
return bits.data();
}
...
}
class_rw_t 中還有一個(gè)指向常量的指針 ro,其中存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議。
/ Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中
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; // 一個(gè)指向常量的指針,其中存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 所遵循的協(xié)議的列表
...
}
// 用于存儲(chǔ)一個(gè) Objective-C 類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList; // (編譯時(shí)確定的)方法列表
protocol_list_t * baseProtocols; // (編譯時(shí)確定的)所屬協(xié)議列表
const ivar_list_t * ivars; // (編譯時(shí)確定的)實(shí)例變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; // (編譯時(shí)確定的)屬性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
加載 ObjC 運(yùn)行時(shí)的過程中在 realizeClass() 方法中:
- 從 class_data_bits_t 調(diào)用 data 方法,將結(jié)果強(qiáng)制轉(zhuǎn)換為 class_ro_t 指針;
- 初始化一個(gè) class_rw_t 結(jié)構(gòu)體;
- 設(shè)置結(jié)構(gòu)體 ro 的值以及 flag。
- 最后重新將這個(gè) class_rw_t 設(shè)置給 class_data_bits_t 的 data。
...
const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
...
在上面這段代碼運(yùn)行之后 class_rw_t 中的方法,屬性以及協(xié)議列表均為空。這時(shí)需要 realizeClass 調(diào)用 methodizeClass 方法來將類自己實(shí)現(xiàn)的方法(包括分類)、屬性和遵循的協(xié)議加載到 methods、 properties 和 protocols 列表中。
方法的結(jié)構(gòu),與類和對(duì)象一樣,方法在內(nèi)存中也是一個(gè)結(jié)構(gòu)體 method_t,其中包括成員變量 name(SEL 類型,實(shí)際上就是方法名)、types(一個(gè)C字符串方法類型,詳見 Type Encodings)、imp(IMP 類型方法實(shí)現(xiàn))。
struct method_t {
SEL name;
const char *types;
IMP imp;
};
結(jié)論:
(1) 在 runtime 初始化之后,realizeClass 之前,從 class_data_bits_t 結(jié)構(gòu)體中獲取的 class_rw_t 一直都不是 class_rw_t 結(jié)構(gòu)體,而是class_ro_t。因?yàn)轭惖囊恍┓椒?、屬性和協(xié)議都是在編譯期決定的(baseMethods 等成員以及類在內(nèi)存中的位置都是編譯期決定的)。
(2) 類在內(nèi)存中的位置是在編譯期間決定的,在之后修改代碼,也不會(huì)改變內(nèi)存中的位置。
類的方法、屬性以及協(xié)議在編譯期間存放到了“錯(cuò)誤”的位置,直到 realizeClass 執(zhí)行之后,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t,這樣我們即可以在運(yùn)行時(shí)為 class_rw_t 添加方法,也不會(huì)影響類的只讀結(jié)構(gòu)。
(3) 在 class_ro_t 中的屬性在運(yùn)行期間就不能改變了,再添加方法時(shí),會(huì)修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods。
(4)一個(gè)類(Class)持有一個(gè)分發(fā)表,在運(yùn)行期分發(fā)消息,表中的每一個(gè)實(shí)體代表一個(gè)方法(Method),它的名字叫做選擇子(SEL),對(duì)應(yīng)著一種方法實(shí)現(xiàn)(IMP)。
參考:
8. 什么是 IMP?什么是選擇器 selector ?
8.1 IMP
IMP 在 runtime 源碼 objc.h 中的定義是:
/// A pointer to the function of a method implementation.
typedef void (*IMP)(void /* id, SEL, ... */ );
它就是一個(gè)函數(shù)指針,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC 消息之后,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的。而 IMP 這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口,我們就可以繞開消息傳遞階段,直接執(zhí)行方法的實(shí)現(xiàn),以達(dá)到更好的性能(在 Mantle 的 MTLModelAdapter.m 中可以看到這方面的應(yīng)用)。
你會(huì)發(fā)現(xiàn) IMP 指向的方法與 objc_msgSend 函數(shù)類型相同,參數(shù)都包含 id 和 SEL 類型。每個(gè)方法名都對(duì)應(yīng)一個(gè) SEL 類型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的 SEL 對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過一組 id 和 SEL 參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址;反之亦然。
8.2 選擇器 selector
選擇器代表方法在 Runtime 期間的標(biāo)識(shí)符,為 SEL 類型,SEL 與普通字符串的區(qū)別在于 SEL 對(duì)于選擇器來說總是能保證其唯一性。在類加載的時(shí)候,編譯器會(huì)生成與方法相對(duì)應(yīng)的選擇子,并注冊(cè)到 Objective-C 的 Runtime 運(yùn)行系統(tǒng)。
SEL 在 objc.h 中的定義是:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL 看上去是一個(gè)指向結(jié)構(gòu)體的指針,但是實(shí)際上是什么類型呢?objc.h 中提供了運(yùn)行時(shí)向系統(tǒng)注冊(cè)選擇器的函數(shù) sel_registerName()。而在開源的 objc-sel.mm 中提供了sel_registerName() 函數(shù)的實(shí)現(xiàn),其中能找到一些蛛絲馬跡:
SEL sel_registerName(const char *name) {
return __sel_registerName(name, 1, 1); // YES lock, YES copy
}
static SEL __sel_registerName(const char *name, int lock, int copy)
{
SEL result = 0;
if (lock) selLock.assertUnlocked();
else selLock.assertWriting();
// name 為空直接返回 0
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
if (lock) selLock.read();
if (namedSelectors) {
// 到全局的表中去找
result = (SEL)NXMapGet(namedSelectors, name);
}
if (lock) selLock.unlockRead();
if (result) return result;
// No match. Insert.
if (lock) selLock.write();
if (!namedSelectors) {
namedSelectors = NXCreateMapTable(NXStrValueMapPrototype,
(unsigned)SelrefCount);
}
if (lock) {
// Rescan in case it was added while we dropped the lock
result = (SEL)NXMapGet(namedSelectors, name);
}
if (!result) {
// 創(chuàng)建一個(gè) SEL
result = sel_alloc(name, copy);
// fixme choose a better container (hash not map for starters)
NXMapInsert(namedSelectors, sel_getName(result), result);
}
if (lock) selLock.unlockWrite();
return result;
}
static SEL sel_alloc(const char *name, bool copy)
{
selLock.assertWriting();
return (SEL)(copy ? strdupIfMutable(name) : name);
}
從創(chuàng)建 SEL 的實(shí)現(xiàn)來看, SEL 實(shí)際上是一個(gè) char * 類型,也就是一個(gè)字符串。
(1) 使用 @selector() 生成的選擇子不會(huì)因?yàn)轭惖牟煌淖儯词狗椒窒嗤兞款愋筒煌矔?huì)導(dǎo)致它們具有相同的方法選擇子),其內(nèi)存地址在編譯期間就已經(jīng)確定了。也就是說向不同的類發(fā)送相同的消息時(shí),其生成的選擇子是完全相同的。
(2) 通過 @selector(方法名) 就可以返回一個(gè)選擇子,通過 (void *)@selector(方法名), 就可以讀取選擇器的地址。
(3) 推斷出的 selector 的特性:
- Objective-C 為我們維護(hù)了一個(gè)巨大的選擇子表
- 在使用
@selector()時(shí)會(huì)從這個(gè)選擇子表中根據(jù)選擇子的名字查找對(duì)應(yīng)的 SEL。如果沒有找到,則會(huì)生成一個(gè)SEL并添加到表中。 - 在編譯期間會(huì)掃描全部的頭文件和實(shí)現(xiàn)文件將其中的方法以及使用
@selector()生成的選擇子加入到選擇子表中。
參考:
9. 消息發(fā)送和消息轉(zhuǎn)發(fā)
具體過程查看源碼中
lookUpImpOrForward()函數(shù)部分的注釋
- 發(fā)送 hello 消息后,編譯器會(huì)將上面這行 [obj hello]; 代碼轉(zhuǎn)成 objc_msgSend()(注:objc_msgSend 是一個(gè)私有方法,而且是用匯編實(shí)現(xiàn)的,我們沒有辦法進(jìn)入它的實(shí)現(xiàn),但是我們可以通過 lookUpImpOrForward 函數(shù)斷點(diǎn)攔截)
- 到當(dāng)前類的緩存中去查找方法實(shí)現(xiàn),如果找到了直接 done
- 如果沒找到,就到當(dāng)前類的方法列表中去查找,如果找到了直接 done
- 如果還沒找到,就到父類的緩存中去查找方法實(shí)現(xiàn),如果找到了直接 done
- 如果沒找到,就到父類的方法列表中去查找,如果找到了直接 done
- 如果還沒找到,就進(jìn)行方法決議
- 最后還沒找到的話,就走消息轉(zhuǎn)發(fā)
參考:
10. Method Swizzling
- 什么是 Method Swizzling ?
- Method Swizzling 有什么注意點(diǎn)?
- Method Swizzling 的原理是什么?
- Method Swizzling 為什么要在 +load 方法中進(jìn)行?
11. Category
- Category 是什么?
- Category 中的方法和屬性以及協(xié)議是怎么存儲(chǔ)和加載的?
- Category 和 Class 的關(guān)系
12. Associated Objects 的原理是什么?到底能不能在 Category 中給 Objective-C 類添加屬性和實(shí)例變量?
- Associated Objects 的原理是什么?
- Associated Objects 的內(nèi)存管理機(jī)制?
- 到底能不能在 Category 中給 Objective-C 類添加屬性和實(shí)例變量?
13. Objective-C 中的 Protocol 是什么?
14. self 和 super 的本質(zhì)
self 和 super 兩個(gè)是不同的,self 是方法的一個(gè)隱藏參數(shù)(每個(gè)方法都有兩個(gè)隱藏的參數(shù),self 和 _cmd),每個(gè)方法的實(shí)現(xiàn)的第一個(gè)參數(shù)即為 self。而 super 不是一個(gè)隱藏參數(shù),它實(shí)際上只是一個(gè)”編譯器標(biāo)示符”,它負(fù)責(zé)告訴編譯器,當(dāng)調(diào)用 [super xxx]方法時(shí),去調(diào)用父類的方法,而不是本類中的方法。
我們可以看看 message.h 中提供的發(fā)消息給父類的函數(shù):
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ );
當(dāng)我們發(fā)送消息給 super 時(shí),runtime 時(shí)就不是使用 objc_msgSend 方法了,而是 objc_msgSendSuper。函數(shù)的第一個(gè)參數(shù)也不再是 self 了,編譯器會(huì)生成一個(gè) objc_super 結(jié)構(gòu)體。下面是 message.h 中 objc_super 結(jié)構(gòu)體的定義:
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
objc_super 包含了兩個(gè)變量,receiver 是消息的實(shí)際接收者,super_class 是指向當(dāng)前類的父類。
通過 clang -rewrite-objc NyanCat.m 命令將下面定義的 NyanCat 類轉(zhuǎn)成 cpp 代碼。
#import <Foundation/Foundation.h>
@interface NyanCat : NSObject
@end
@implementation NyanCat
- (instancetype)init {
self = [super init];
return self;
}
@end
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
#ifndef _REWRITER_typedef_NyanCat
#define _REWRITER_typedef_NyanCat
typedef struct objc_object NyanCat;
typedef struct {} _objc_exc_NyanCat;
#endif
struct NyanCat_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
/* @end */
// @implementation NyanCat
static instancetype _I_NyanCat_init(NyanCat * self, SEL _cmd) {
self = ((NyanCat *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("NyanCat"))}, sel_registerName("init"));
return self;
}
// ...
由此可見,[super xxxx] 在運(yùn)行時(shí)確實(shí)被轉(zhuǎn)成了 objc_msgSendSuper 函數(shù)。
那么 objc_msgSendSuper 這個(gè)函數(shù)的內(nèi)部實(shí)現(xiàn)是怎么樣的呢?文檔 objc_msgSendSuper 函數(shù)的注釋中對(duì) super 參數(shù)的注釋是這樣寫的:
super - A pointer to an objc_super data structure. Pass values identifying the context the message was sent to, including the instance of the class that is to receive the message and the superclass at which to start searching for the method implementation.
我們可以推斷出,objc_msgSendSuper 函數(shù)實(shí)現(xiàn)實(shí)際上就是:從 objc_super 結(jié)構(gòu)體指向的 objc_super->superClass 的方法列表開始查找調(diào)用方法的 selector 對(duì)應(yīng)的實(shí)現(xiàn),找到后以 objc_super->receiver 去調(diào)用這個(gè) selector,最后就變成了調(diào)用 objc_msgSend 函數(shù)給 self 發(fā)消息的形式了。
objc_msgSend(objc_super->receiver, @selector(xxx));
這里的 objc_super->receiver 就相當(dāng)于 self,上面的操作其實(shí)就是:
objc_msgSend(self, @selector(xxx));
[self init] 和 [super init] 的相同點(diǎn)在于消息接收者實(shí)際上都是 self(方法調(diào)用源頭),區(qū)別就在于查找方法的實(shí)現(xiàn)時(shí),前者是從 currentClass(self 所屬的類)的方法列表中開始往上找,而后者是從 objc_super->spuerClass(也就是調(diào)用了 super 的地方的父類,這是在編譯時(shí)就確定了的)的方法列表中開始往上查找。
需要強(qiáng)調(diào)的地方是,[self xxx] 要調(diào)用的實(shí)現(xiàn)是在運(yùn)行時(shí)動(dòng)態(tài)決定的,而 [super xxx] 要調(diào)用的實(shí)現(xiàn)是編譯時(shí)就確定了的(這里有個(gè)例子可以測(cè)試一下)。從上面轉(zhuǎn)換出來的 cpp 代碼中也可以看出來,這其實(shí)是因?yàn)?objc_msgSendSuper 函數(shù)的第一個(gè)參數(shù) objc_super 結(jié)構(gòu)體中的 receiver 是通過接收方法中的 self 參數(shù)得來的,所以動(dòng)態(tài)決定的,而 objc_super->superClass 是通過 class_getSuperclass(objc_getClass("NyanCat")) 得到的,所以是靜態(tài)的,在編譯時(shí)就確定了的。
static instancetype _I_NyanCat_init(NyanCat * self, SEL _cmd) {
self = ((NyanCat *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("NyanCat"))}, sel_registerName("init"));
return self;
}
參考:
- Objc Runtime
- Objective C: Difference between self and super
- What does it mean when you assign [super init] to self?
15. load 方法和 initialize 方法
-
+load方法和+initialize方法分別在什么時(shí)候被調(diào)用? - 這兩個(gè)方法是用來干嘛的?
- ProtocolKit 的實(shí)現(xiàn)中為什么要在 main 函數(shù)執(zhí)行前進(jìn)行 Protocol 方法默認(rèn)實(shí)現(xiàn)的注冊(cè)?
延伸
- clang 命令的使用(比如
clang -rewrite-objc test.m),clang -rewrite-objc的作用是什么?clang rewrite 出來的文件跟 objc runtime 源碼的實(shí)現(xiàn)有什么區(qū)別嗎?
參考:
- Understanding the Objective-C Runtime
- Objective-C Runtime - 玉令天下的博客
- Objective-C 中的類和對(duì)象 - ibireme 的博客
- Draveness 出品的 runtime 源碼閱讀系列文章(強(qiáng)烈推薦)
- 對(duì)象是如何初始化的(iOS):介紹了 Objective-C 對(duì)象初始化的過程
- 從 NSObject 的初始化了解 isa:深入剖析了 isa 的結(jié)構(gòu)和作用
- 深入解析 ObjC 中方法的結(jié)構(gòu):介紹了在 ObjC 中是如何存儲(chǔ)方法的
- 從源代碼看 ObjC 中消息的發(fā)送 :通過逐步斷點(diǎn)調(diào)試 objc 源碼的方式,從 Objc 源代碼中分析并合理地推測(cè)一些關(guān)于消息傳遞的過程
- 從 ObjC Runtime 源碼分析一個(gè)對(duì)象創(chuàng)建的過程
- Objective-C 對(duì)象模型 - 雷純鋒的技術(shù)博客
- Objc 對(duì)象的今生今世
- Runtime源碼 —— 概述和調(diào)試環(huán)境準(zhǔn)備:作者寫了一個(gè)系列的文章,內(nèi)容很詳細(xì)
- Objective-C runtime - 系列開始:簡(jiǎn)單介紹了學(xué)習(xí) objc 源代碼的方法