注:本文始發(fā)于個人 GitHub 項目 ShannonChenCHN/iOSDevLevelingUp恰梢。
關(guān)于 objc4 源碼的一些說明:
- objc4 的源碼不能直接編譯,需要配置相關(guān)環(huán)境才能運(yùn)行丈牢×矍啵可以在這里下載可調(diào)式的源碼衙四。
- objc 運(yùn)行時源碼的入口在
void _objc_init(void)
函數(shù)铃肯。
目錄
- 1.Objective-C 對象是什么?Class 是什么传蹈?id 又是什么押逼?
- 2.isa 是什么步藕?為什么要有 isa?
- 3.為什么在 Objective-C 中挑格,所以的對象都用一個指針來追蹤咙冗?
- 4.Objective-C 對象是如何被創(chuàng)建(alloc)和初始化(init)的?
- 5.Objective-C 對象的實例變量是什么漂彤?為什么不能給 Objective-C 對象動態(tài)添加實例變量雾消?
- 6.Objective-C 對象的屬性是什么?屬性跟實例變量的區(qū)別挫望?
- 7.Objective-C 對象的方法是什么立润?Objective-C 對象的方法在內(nèi)存中的存儲結(jié)構(gòu)是什么樣的?
- 8.什么是 IMP媳板?什么是選擇器 selector 桑腮?
- 9.消息發(fā)送和消息轉(zhuǎn)發(fā)
- 10.Method Swizzling
- 11.Category
- 12.Associated Objects 的原理是什么?到底能不能在 Category 中給 Objective-C 類添加屬性和實例變量蛉幸?
- 13.Objective-C 中的 Protocol 是什么破讨?
- 14.
self
和super
的本質(zhì) - 15.
load
方法和initialize
方法
1. Objective-C 對象是什么?Class 是什么奕纫?id 又是什么提陶?
所有的類都繼承 NSObject 或者 NSProxy,先來看看這兩個類在各自的公開頭文件中的定義:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
@interface NSProxy <NSObject> {
Class isa;
}
在 objc.h 文件中匹层,對于 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 文件中對 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 中,每一個對象是一個結(jié)構(gòu)體又固,每個對象都有一個 isa 指針,類對象 Class 也是一個對象煤率。所以仰冠,我們說,凡是包含 isa 指針的蝶糯,都可以被認(rèn)為是 Objective-C 中的對象洋只。運(yùn)行時可以通過 isa 指針,查找到該對象是屬于什么類(Class)昼捍。
2. isa 是什么识虚?為什么要有 isa?
在 Runtime 源碼中妒茬,對于 objc_object 和 objc_class 的定義分別如下:
struct objc_object {
private:
isa_t isa; // isa 是一個 union 聯(lián)合體担锤,其包含這個對象所屬類的信息
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,其實繼承了 objc_object 的 isa , 在這里 isa 是一個指向元類的指針
// 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 用于存儲類的方法肛循、屬性铭腕、遵循的協(xié)議等信息的地方
// 針對 class_data_bits_t 的 data() 函數(shù)的封裝,最終返回一個 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 也是一個 objc_object,objc_object 和 objc_class 都有一個成員變量 isa夹孔。isa 變量的類型是 isa_t被盈,這個 isa_t 其實是一個聯(lián)合體(union),其中包括成員量 cls搭伤。也就是說只怎,每個 objc_object 通過自己持有的 isa,都可以查找到自己所屬的類闷畸,對于 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 的類型是一個 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)體的部分,訪問對象的 isa 會直接返回一個指向 cls 的指針殿漠,也就是在 iPhone 遷移到 64 位系統(tǒng)之前時 isa 的類型赴精。1 表示當(dāng)前 isa 不是指針,但是其中也有 cls 的信息绞幌,只是其中關(guān)于類的指針都是保存在 shiftcls 中蕾哟。
uintptr_t has_assoc : 1; // 對象含有或者曾經(jīng)含有關(guān)聯(lián)引用,沒有關(guān)聯(lián)引用的可以更快地釋放內(nèi)存
uintptr_t has_cxx_dtor : 1; // 表示當(dāng)前對象有 C++ 或者 ObjC 的析構(gòu)器(destructor)莲蜘,如果沒有析構(gòu)器就會快速釋放內(nèi)存谭确。
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6; // 用于調(diào)試器判斷當(dāng)前對象是真的對象還是沒有初始化的空間
uintptr_t weakly_referenced : 1; // 對象被指向或者曾經(jīng)指向一個 ARC 的弱變量,沒有弱引用的對象可以更快釋放
uintptr_t deallocating : 1; // 對象正在釋放內(nèi)存
uintptr_t has_sidetable_rc : 1; // 對象的引用計數(shù)太大了票渠,存不下
uintptr_t extra_rc : 8; // 對象的引用計數(shù)超過 1逐哈,會存在這個這個里面,如果引用計數(shù)為 10问顷,extra_rc 的值就為 9
};
};
而在 Objective-C 中昂秃,對象的方法都是存儲在類中,而不是對象中(如果每一個對象都保存了自己能執(zhí)行的方法杜窄,那么對內(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; // 一個指向常量的指針塞耕,其中存儲了當(dāng)前類在編譯期就已經(jīng)確定的屬性蚀腿、方法以及遵循的協(xié)議
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 所遵循的協(xié)議的列表
Class firstSubclass;
Class nextSiblingClass;
...
};
當(dāng)一個對象的實例方法被調(diào)用時,它要通過自己持有的 isa 來查找對應(yīng)的類扫外,然后在這里的 class_data_bits_t 結(jié)構(gòu)體中查找對應(yīng)方法的實現(xiàn)(每個對象可以通過 cls->data()-> methods
來訪問所屬類的方法)唯咬。同時纱注,每一個 objc_class 也有一個指向自己的父類的指針 super_class 用來查找繼承的方法。
因為在 Objective-C 中胆胰,類其實也是一個對象狞贱,每個類也有一個 isa 指向自己所屬的元類。所以無論是類還是對象都能通過相同的機(jī)制查找方法的實現(xiàn)蜀涨。
isa 在方法調(diào)用時扮演的角色:
- 調(diào)用一個對象的實例方法時瞎嬉,通過對象的 isa 在類中獲取方法的實現(xiàn)
- 調(diào)用一個類的類方法時,通過類的 isa 在元類中獲取方法的實現(xiàn)
- 如果在當(dāng)前類/元類中沒找到厚柳,就會通過類/元類的 superclass 在繼承鏈中一級一級往上查找
<div align='center'>圖 1. 對象氧枣,類與元類之間的關(guān)系</div>
isa_t 中包含什么:
isa 的類型 isa_t 是一個 union 類型的結(jié)構(gòu)體,也就是說其中的 isa_t别垮、cls便监、 bits 還有結(jié)構(gòu)體共用同一塊地址空間,而 isa 總共會占據(jù) 64 位的內(nèi)存空間(決定于其中的結(jié)構(gòu)體)碳想。其中包含的信息見上面的代碼注釋烧董。
現(xiàn)在直接訪問對象(objc_object)的 isa 已經(jīng)不會返回類指針了,取而代之的是使用 ISA()
方法來獲取類指針胧奔。其中 ISA_MASK 是宏定義逊移,這里通過掩碼的方式獲取類指針。
結(jié)論
(1)isa 的作用:用于查找對象(或類對象)所屬類(或元類)的信息龙填,比如方法列表胳泉。
(2)isa 是什么:isa 的數(shù)據(jù)結(jié)構(gòu)是一個 isa_t 聯(lián)合體,其中包含其所屬的 Class 的地址岩遗,通過訪問對象的 isa扇商,就可以獲取到指向其所屬 Class 的指針(針對 tagged pointer 的情況,也就是 non-pointer isa宿礁,有點(diǎn)不一樣的是案铺,除了指向 class 的指針,isa 中還會包含對象本身的一些信息窘拯,比如對象是否被弱引用)红且。
3. 為什么在 Objective-C 中坝茎,所以的對象都用一個指針來追蹤涤姊?
內(nèi)存中的數(shù)據(jù)類型分為兩種:值類型和引用類型。指針就是引用類型嗤放,struct 類型就是值類型思喊。
值類型在傳值時需要拷貝內(nèi)容本身,而引用類型在傳遞時次酌,拷貝的是對象的地址恨课。所以舆乔,一方面,值類型的傳遞占用更多的內(nèi)存空間剂公,使用引用類型更節(jié)省內(nèi)存開銷希俩;另一方面,也是最主要的原因纲辽,很多時候颜武,我們需要把一個對象交給另一個函數(shù)或者方法去修改其中的內(nèi)容(比如說一個 Person 對象的 age 屬性),顯然如果我們想讓修改方獲取到這個對象拖吼,我們需要的傳遞的是地址鳞上,而不是復(fù)制一份。
對于像 int
這樣的基本數(shù)據(jù)類型吊档,拷貝起來更快篙议,而且數(shù)據(jù)簡單,方便修改怠硼,所以就不用指針了鬼贱。
另一方面,對象的內(nèi)存是分配在堆上的拒名,而值類型是分配到棧上的吩愧。所以一般對象的生命周期會比普通的值類型要長,而且創(chuàng)建和銷毀對象以及內(nèi)存管理是要消耗性能的增显,所以通過指針來引用一個對象雁佳,比直接復(fù)制和創(chuàng)建對象要更有效率、更節(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 對象是如何被創(chuàng)建(alloc)和初始化(init)的糖权?
整個對象的創(chuàng)建過程其實就做了兩件事情:為對象分配內(nèi)存空間,以及初始化 isa(一個聯(lián)合體)炸站。
(1)創(chuàng)建 NSObject 對象的過程
+ (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) {
// 實例變量內(nèi)存大小星澳,實例大小 instanceSize 會存儲在類的 isa_t 結(jié)構(gòu)體中,經(jīng)過對齊最后返回
size_t size = cls->instanceSize(extraBytes);
// 給對象申請內(nèi)存空間
id obj = (id)calloc(1, size);
if (!obj) return nil;
// 初始化 isa
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
獲取對象內(nèi)存空間大泻狄住:
size_t instanceSize(size_t extraBytes) {
// Core Foundation 需要所有的對象的大小至少有 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 是一個 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 對象初始化的過程
NSObject 對象的初始化實際上就是返回 +alloc
執(zhí)行后得到的對象本身:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj) {
return obj;
}
5. Objective-C 對象的實例變量是什么阀坏?為什么不能給 Objective-C 對象動態(tài)添加實例變量如暖?
(1)兩個注意點(diǎn):
- Objective-C 的
->
操作符不是C語言指針操作 - Objective-C 對象不能簡單對應(yīng)于一個 C struct,訪問成員變量不等于訪問 C struct 成員
(2)Non Fragile ivars
在 Runtime 的現(xiàn)行版本中忌堂,最大的特點(diǎn)就是健壯的實例變量盒至。
當(dāng)一個類被編譯時,實例變量的布局也就形成了,它表明訪問類的實例變量的位置枷遂。用舊版OSX SDK 編譯的 MyObject 類成員變量布局是這樣的樱衷,MyObject的成員變量依次排列在基類NSObject 的成員后面。
當(dāng)蘋果發(fā)布新版本OSX SDK后酒唉,NSObject增加了兩個成員變量矩桂。如果沒有Non Fragile ivars特性,我們的代碼將無法正常運(yùn)行痪伦,因為MyObject類成員變量布局在編譯時已經(jīng)確定耍鬓,有兩個成員變量和基類的內(nèi)存區(qū)域重疊了。此時流妻,我們只能重新編譯MyObject代碼牲蜀,程序才能在新版本系統(tǒng)上運(yùn)行。
現(xiàn)在有了 Non Fragile ivars 之后绅这,問題就解決了涣达。在程序啟動后,runtime加載MyObject類的時候证薇,通過計算基類的大小度苔,runtime 動態(tài)調(diào)整了 MyObject 類成員變量布局,把MyObject成員變量的位置向后移動8個字節(jié)浑度。于是我們的程序無需編譯寇窑,就能在新版本系統(tǒng)上運(yùn)行。
(3)Non Fragile ivars 是如何實現(xiàn)的呢箩张?
當(dāng)成員變量布局調(diào)整后甩骏,靜態(tài)編譯的native程序怎么能找到變量的新偏移位置呢?
根據(jù) Runtime 源碼可知先慷,一個變量實際上就是一個 ivar_t 結(jié)構(gòu)體饮笛。而每個 Objective-C 對象對應(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
變量脓诡,可以取得類里任意一個類成員變量的定義无午。
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; // 一個指向常量的指針,其中存儲了當(dāng)前類在編譯期就已經(jīng)確定的屬性祝谚、方法以及遵循的協(xié)議
//...
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // 實例變量大小宪迟,決定對象創(chuàng)建時要分配的內(nèi)存
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 類名
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;
}
};
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)該就是用來記錄這個成員變量在對象中的偏移位置。也就是說调炬,runtime在發(fā)現(xiàn)基類大小變化時拒课,通過修改offset涩咖,來更新子類成員變量的偏移值。
假如我們現(xiàn)在有一個繼承于 NSError 的類 MyClass袍辞,在編譯時,LLVM計算出基類 NSError 對象的大小為40字節(jié),然后記錄在MyClass的類定義中袭异,如下是對應(yīng)的C代碼。在編譯后的可執(zhí)行程序中炬藤,寫死了“40”這個魔術(shù)數(shù)字御铃,記錄了在此次編譯時MyClass基類的大小。
class_ro_t class_ro_MyClass = {
.instanceStart = 40,
.instanceSize = 48,
//...
}
現(xiàn)在假如蘋果發(fā)布了OSX 11 SDK沈矿,NSError類大小增加到48字節(jié)上真。當(dāng)我們的程序啟動后,runtime加載MyClass類定義的時候羹膳,發(fā)現(xiàn)基類的真實大小和MyClass的instanceStart不相符睡互,得知基類的大小發(fā)生了改變。于是runtime遍歷MyClass的所有成員變量定義陵像,將offset指向的值增加8就珠。具體的實現(xiàn)代碼在runtime/objc-runtime-new.mm的 moveIvars()
函數(shù)中。
并且醒颖,MyClass類定義的instanceSize也要增加8妻怎。這樣runtime在創(chuàng)建MyClass對象的時候,能分配出正確大小的內(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類不能動態(tài)添加成員變量
runtime 提供了一個 class_addIvar() 函數(shù)用于給類添加成員變量逼侦,但是根據(jù)文檔中的注釋,這個函數(shù)只能在“構(gòu)建一個類的過程中”調(diào)用腰耙。一旦完成類定義偿洁,就不能再添加成員變量了。經(jīng)過編譯的類在程序啟動后就被runtime加載沟优,沒有機(jī)會調(diào)用addIvar涕滋。程序在運(yùn)行時動態(tài)構(gòu)建的類需要在調(diào)用 objc_allocateClassPair 之后,調(diào)用 objc_registerClassPair 之前才可以添加成員變量挠阁。
這樣做會帶來嚴(yán)重問題宾肺,為基類動態(tài)增加成員變量會導(dǎo)致所有已創(chuàng)建出的子類實例都無法使用。那為什么runtime允許動態(tài)添加方法和屬性侵俗,而不會引發(fā)問題呢锨用?
因為方法和屬性并不“屬于”實例,而成員變量“屬于”實例隘谣。我們所說的“類實例”概念增拥,指的是一塊內(nèi)存區(qū)域啄巧,包含了isa指針和所有的成員變量。所以假如允許動態(tài)修改類成員變量布局掌栅,已經(jīng)創(chuàng)建出的實例就不符合類定義了秩仆,變成了無效對象。但方法定義是在objc_class中管理的猾封,不管如何增刪類方法澄耍,都不影響實例的內(nèi)存布局,已經(jīng)創(chuàng)建出的實例仍然可正常使用晌缘。
美團(tuán)的技術(shù)博客中給出的解釋比較簡單齐莲,其中“破壞破壞類的內(nèi)部布局”這句話本身也是有些問題的:
extension在編譯期決議,它就是類的一部分磷箕,在編譯期和頭文件里的@interface以及實現(xiàn)文件里的@implement一起形成一個完整的類选酗,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡岳枷。extension一般用來隱藏類的私有信息星掰,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension嫩舟。
但是category則完全不一樣氢烘,它是在運(yùn)行期決議的。
就category和extension的區(qū)別來看家厌,我們可以推導(dǎo)出一個明顯的事實播玖,extension可以添加實例變量,而category是無法添加實例變量的(因為在運(yùn)行期饭于,對象的內(nèi)存布局已經(jīng)確定蜀踏,如果添加實例變量就會破壞類的內(nèi)部布局,這對編譯型語言來說是災(zāi)難性的)掰吕。
參考:
6. Objective-C 對象的屬性是什么果覆?屬性跟實例變量的區(qū)別?
屬性是一個結(jié)構(gòu)體殖熟,其中包含屬性名和屬性本身的屬性(attributes)局待。
我們一般是通過使用 @property
進(jìn)行屬性定義,編譯時編譯器會自動生成對應(yīng)的實例變量(默認(rèn)情況下生成的實例變量名是在對應(yīng)的屬性名前加了下劃線“_”)菱属,同時還會自動合成對應(yīng)的 setter 和 getter 方法用于存取屬性值钳榨。
我們可以驗證一下,先定義一個帶有屬性的類 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 類實際上就是一個 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 對應(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);
// 實例變量列表
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}}
};
// 實例方法列表
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
對應(yīng)的屬性名和屬性的 attributes(attributes 字符串所代表的含義可以在官方文檔上查閱到)。 - 實例變量列表
_method_list_t
中也有屬性cost
對應(yīng)的變量信息赏陵,變量名為_cost
饼齿,類型為@"NSString"
饲漾。 - 實例方法列表
_method_list_t
中有屬性cost
對應(yīng)的 setter 和 getter 方法,這兩個方法的實現(xiàn)分別對應(yīng)的是兩個函數(shù)——_I_NyanCat_setCost_(NyanCat * self, SEL _cmd, NSString *cost)
和_I_NyanCat_cost(NyanCat * self, SEL _cmd)
缕溉。
以上三條結(jié)果正好驗證了我們一開始提出的結(jié)論考传。
實際上,在 runtime 源碼 objc-runtime-new.h 的實現(xiàn)中倒淫,屬性就是一個 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 的屬性
};
在實際使用 runtime 時敌土,通過下面兩個函數(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é):
- 一個對象的屬性實際上包括實例變量以及存取屬性值(實際上就是實例變量值)的 setter/getter 方法兩部分(這里只討論類本身定義的屬性运翼,category 中的 @property 并沒有為我們生成實例變量以及存取方法返干,而需要我們手動實現(xiàn))。
- 屬性的實際結(jié)構(gòu)是一個結(jié)構(gòu)體血淌,其中包含屬性名和 attributes 兩部分矩欠。
7. Objective-C 對象的方法是什么?Objective-C 對象的方法在內(nèi)存中的存儲結(jié)構(gòu)是什么樣的悠夯?
objc_class 有一個 class_data_bits_t
類型的變量 bits癌淮,Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中沦补,通過調(diào)用 objc_class 的 class_rw_t *data() 方法乳蓄,可以獲取這個 class_rw_t 類型的變量。
// Objective-C 類是一個結(jié)構(gòu)體夕膀,繼承于 objc_object
struct objc_class : objc_object {
// 這里沒寫 isa虚倒,其實繼承了 objc_object 的 isa , 在這里 isa 是一個指向元類的指針
// 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 用于存儲類的方法产舞、屬性魂奥、遵循的協(xié)議等信息的地方
// 針對 class_data_bits_t 的 data() 函數(shù)的封裝,最終返回一個 class_rw_t 類型的結(jié)構(gòu)體變量
// Objective-C 類中的屬性易猫、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中
class_rw_t *data() {
return bits.data();
}
...
}
class_rw_t 中還有一個指向常量的指針 ro耻煤,其中存儲了當(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; // 一個指向常量的指針,其中存儲了當(dāng)前類在編譯期就已經(jīng)確定的屬性瞬场、方法以及遵循的協(xié)議
method_array_t methods; // 方法列表
property_array_t properties; // 屬性列表
protocol_array_t protocols; // 所遵循的協(xié)議的列表
...
}
// 用于存儲一個 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; // (編譯時確定的)方法列表
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 運(yùn)行時的過程中在 realizeClass()
方法中:
- 從 class_data_bits_t 調(diào)用 data 方法,將結(jié)果強(qiáng)制轉(zhuǎn)換為 class_ro_t 指針贯被;
- 初始化一個 class_rw_t 結(jié)構(gòu)體眼五;
- 設(shè)置結(jié)構(gòu)體 ro 的值以及 flag妆艘。
- 最后重新將這個 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é)議列表均為空批旺。這時需要 realizeClass
調(diào)用 methodizeClass
方法來將類自己實現(xiàn)的方法(包括分類)、屬性和遵循的協(xié)議加載到 methods诵姜、 properties 和 protocols 列表中汽煮。
方法的結(jié)構(gòu),與類和對象一樣棚唆,方法在內(nèi)存中也是一個結(jié)構(gòu)體 method_t暇赤,其中包括成員變量 name(SEL 類型,實際上就是方法名)宵凌、types(一個C字符串方法類型鞋囊,詳見 Type Encodings)、imp(IMP 類型方法實現(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挺益。因為類的一些方法、屬性和協(xié)議都是在編譯期決定的(baseMethods 等成員以及類在內(nèi)存中的位置都是編譯期決定的)乘寒。
(2) 類在內(nèi)存中的位置是在編譯期間決定的矩肩,在之后修改代碼,也不會改變內(nèi)存中的位置肃续。
類的方法黍檩、屬性以及協(xié)議在編譯期間存放到了“錯誤”的位置,直到 realizeClass 執(zhí)行之后始锚,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t刽酱,這樣我們即可以在運(yùn)行時為 class_rw_t 添加方法,也不會影響類的只讀結(jié)構(gòu)瞧捌。
(3) 在 class_ro_t 中的屬性在運(yùn)行期間就不能改變了棵里,再添加方法時,會修改 class_rw_t 中的 methods 列表姐呐,而不是 class_ro_t 中的 baseMethods殿怜。
(4)一個類(Class)持有一個分發(fā)表,在運(yùn)行期分發(fā)消息曙砂,表中的每一個實體代表一個方法(Method)头谜,它的名字叫做選擇子(SEL),對應(yīng)著一種方法實現(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, ... */ );
它就是一個函數(shù)指針,這是由編譯器生成的际度。當(dāng)你發(fā)起一個 ObjC 消息之后葵袭,最終它會執(zhí)行的那段代碼,就是由這個函數(shù)指針指定的乖菱。而 IMP 這個函數(shù)指針就指向了這個方法的實現(xiàn)坡锡。既然得到了執(zhí)行某個實例某個方法的入口,我們就可以繞開消息傳遞階段窒所,直接執(zhí)行方法的實現(xiàn)鹉勒,以達(dá)到更好的性能(在 Mantle 的 MTLModelAdapter.m
中可以看到這方面的應(yīng)用)。
你會發(fā)現(xiàn) IMP 指向的方法與 objc_msgSend 函數(shù)類型相同墩新,參數(shù)都包含 id 和 SEL 類型贸弥。每個方法名都對應(yīng)一個 SEL 類型的方法選擇器窟坐,而每個實例對象中的 SEL 對應(yīng)的方法實現(xiàn)肯定是唯一的海渊,通過一組 id 和 SEL 參數(shù)就能確定唯一的方法實現(xiàn)地址;反之亦然哲鸳。
8.2 選擇器 selector
選擇器代表方法在 Runtime 期間的標(biāo)識符臣疑,為 SEL 類型,SEL 與普通字符串的區(qū)別在于 SEL 對于選擇器來說總是能保證其唯一性徙菠。在類加載的時候讯沈,編譯器會生成與方法相對應(yīng)的選擇子,并注冊到 Objective-C 的 Runtime 運(yùn)行系統(tǒng)婿奔。
SEL 在 objc.h 中的定義是:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL 看上去是一個指向結(jié)構(gòu)體的指針缺狠,但是實際上是什么類型呢?objc.h 中提供了運(yùn)行時向系統(tǒng)注冊選擇器的函數(shù) sel_registerName()
萍摊。而在開源的 objc-sel.mm
中提供了sel_registerName()
函數(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)建一個 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 的實現(xiàn)來看, SEL 實際上是一個 char *
類型冰木,也就是一個字符串穷劈。
(1) 使用 @selector()
生成的選擇子不會因為類的不同而改變(即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇子),其內(nèi)存地址在編譯期間就已經(jīng)確定了踊沸。也就是說向不同的類發(fā)送相同的消息時歇终,其生成的選擇子是完全相同的。
(2) 通過 @selector(方法名)
就可以返回一個選擇子逼龟,通過 (void *)@selector(方法名)
评凝, 就可以讀取選擇器的地址。
(3) 推斷出的 selector 的特性:
- Objective-C 為我們維護(hù)了一個巨大的選擇子表
- 在使用
@selector()
時會從這個選擇子表中根據(jù)選擇子的名字查找對應(yīng)的 SEL腺律。如果沒有找到肥哎,則會生成一個SEL
并添加到表中辽俗。 - 在編譯期間會掃描全部的頭文件和實現(xiàn)文件將其中的方法以及使用
@selector()
生成的選擇子加入到選擇子表中。
參考:
9. 消息發(fā)送和消息轉(zhuǎn)發(fā)
具體過程查看源碼中
lookUpImpOrForward()
函數(shù)部分的注釋
- 發(fā)送 hello 消息后篡诽,編譯器會將上面這行 [obj hello]; 代碼轉(zhuǎn)成 objc_msgSend()(注:objc_msgSend 是一個私有方法崖飘,而且是用匯編實現(xiàn)的,我們沒有辦法進(jìn)入它的實現(xiàn)杈女,但是我們可以通過 lookUpImpOrForward 函數(shù)斷點(diǎn)攔截)
- 到當(dāng)前類的緩存中去查找方法實現(xiàn)朱浴,如果找到了直接 done
- 如果沒找到,就到當(dāng)前類的方法列表中去查找达椰,如果找到了直接 done
- 如果還沒找到翰蠢,就到父類的緩存中去查找方法實現(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é)議是怎么存儲和加載的藕甩?
- Category 和 Class 的關(guān)系
12. Associated Objects 的原理是什么施敢?到底能不能在 Category 中給 Objective-C 類添加屬性和實例變量?
- Associated Objects 的原理是什么狭莱?
- Associated Objects 的內(nèi)存管理機(jī)制僵娃?
- 到底能不能在 Category 中給 Objective-C 類添加屬性和實例變量?
13. Objective-C 中的 Protocol 是什么腋妙?
14. self
和 super
的本質(zhì)
self 和 super 兩個是不同的默怨,self 是方法的一個隱藏參數(shù)(每個方法都有兩個隱藏的參數(shù),self
和 _cmd
)辉阶,每個方法的實現(xiàn)的第一個參數(shù)即為 self先壕。而 super 不是一個隱藏參數(shù),它實際上只是一個”編譯器標(biāo)示符”谆甜,它負(fù)責(zé)告訴編譯器垃僚,當(dāng)調(diào)用 [super xxx]方法時,去調(diào)用父類的方法规辱,而不是本類中的方法谆棺。
我們可以看看 message.h 中提供的發(fā)消息給父類的函數(shù):
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ );
當(dāng)我們發(fā)送消息給 super 時,runtime 時就不是使用 objc_msgSend 方法了,而是 objc_msgSendSuper改淑。函數(shù)的第一個參數(shù)也不再是 self 了碍岔,編譯器會生成一個 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 包含了兩個變量朵夏,receiver 是消息的實際接收者蔼啦,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)行時確實被轉(zhuǎn)成了 objc_msgSendSuper 函數(shù)。
那么 objc_msgSendSuper 這個函數(shù)的內(nèi)部實現(xiàn)是怎么樣的呢饥侵?文檔 objc_msgSendSuper 函數(shù)的注釋中對 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ù)實現(xiàn)實際上就是:從 objc_super
結(jié)構(gòu)體指向的 objc_super->superClass
的方法列表開始查找調(diào)用方法的 selector 對應(yīng)的實現(xiàn),找到后以 objc_super->receiver
去調(diào)用這個 selector躏升,最后就變成了調(diào)用 objc_msgSend 函數(shù)給 self 發(fā)消息的形式了辩棒。
objc_msgSend(objc_super->receiver, @selector(xxx));
這里的 objc_super->receiver 就相當(dāng)于 self,上面的操作其實就是:
objc_msgSend(self, @selector(xxx));
[self init]
和 [super init]
的相同點(diǎn)在于消息接收者實際上都是 self(方法調(diào)用源頭)膨疏,區(qū)別就在于查找方法的實現(xiàn)時一睁,前者是從 currentClass(self 所屬的類)的方法列表中開始往上找,而后者是從 objc_super->spuerClass(也就是調(diào)用了 super 的地方的父類成肘,這是在編譯時就確定了的)的方法列表中開始往上查找卖局。
需要強(qiáng)調(diào)的地方是斧蜕,[self xxx]
要調(diào)用的實現(xiàn)是在運(yùn)行時動態(tài)決定的双霍,而 [super xxx]
要調(diào)用的實現(xiàn)是編譯時就確定了的(這里有個例子可以測試一下)。從上面轉(zhuǎn)換出來的 cpp 代碼中也可以看出來批销,這其實是因為 objc_msgSendSuper
函數(shù)的第一個參數(shù) objc_super
結(jié)構(gòu)體中的 receiver
是通過接收方法中的 self 參數(shù)得來的洒闸,所以動態(tài)決定的,而 objc_super->superClass
是通過 class_getSuperclass(objc_getClass("NyanCat"))
得到的均芽,所以是靜態(tài)的丘逸,在編譯時就確定了的。
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
方法分別在什么時候被調(diào)用掀宋? - 這兩個方法是用來干嘛的深纲?
- ProtocolKit 的實現(xiàn)中為什么要在 main 函數(shù)執(zhí)行前進(jìn)行 Protocol 方法默認(rèn)實現(xiàn)的注冊?
延伸
- clang 命令的使用(比如
clang -rewrite-objc test.m
)劲妙,clang -rewrite-objc
的作用是什么湃鹊?clang rewrite 出來的文件跟 objc runtime 源碼的實現(xiàn)有什么區(qū)別嗎?
參考:
- Understanding the Objective-C Runtime
- Objective-C Runtime - 玉令天下的博客
- Objective-C 中的類和對象 - ibireme 的博客
- Draveness 出品的 runtime 源碼閱讀系列文章(強(qiáng)烈推薦)
- 對象是如何初始化的(iOS):介紹了 Objective-C 對象初始化的過程
- 從 NSObject 的初始化了解 isa:深入剖析了 isa 的結(jié)構(gòu)和作用
- 深入解析 ObjC 中方法的結(jié)構(gòu):介紹了在 ObjC 中是如何存儲方法的
- 從源代碼看 ObjC 中消息的發(fā)送 :通過逐步斷點(diǎn)調(diào)試 objc 源碼的方式镣奋,從 Objc 源代碼中分析并合理地推測一些關(guān)于消息傳遞的過程
- 從 ObjC Runtime 源碼分析一個對象創(chuàng)建的過程
- Objective-C 對象模型 - 雷純鋒的技術(shù)博客
- Objc 對象的今生今世
- Runtime源碼 —— 概述和調(diào)試環(huán)境準(zhǔn)備:作者寫了一個系列的文章币呵,內(nèi)容很詳細(xì)
- Objective-C runtime - 系列開始:簡單介紹了學(xué)習(xí) objc 源代碼的方法