本文基于objc4-709源碼進(jìn)行分析。關(guān)于源碼編譯:objc - 編譯Runtime源碼objc4-706
objc中的類和對(duì)象
1.類和對(duì)象的結(jié)構(gòu)概要
NSObject是所有類的基類,NSObject在源碼中的定義:
在NSObject.mm文件中找到
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
NSObject類的第一個(gè)成員變量就是Class類型的isa疗绣。
Object.mm文件:
typedef struct objc_class *Class; //類
typedef struct objc_object *id;
@interface Object {
Class isa;
}
Class就是c語言定義的objc_class結(jié)構(gòu)體類型的指針歉眷,objc中的類實(shí)際上就是objc_class。
而id類型就是objc_object結(jié)構(gòu)體類型的指針(就是我們平時(shí)經(jīng)常用到的id類型)抒巢,我們平時(shí)用的id可以用來聲明一個(gè)對(duì)象,說明objc中的對(duì)象實(shí)際上就是objc_object热幔。
objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA;
Class superclass; //父類的指針
cache_t cache; // formerly cache pointer and vtable 方法緩存
class_data_bits_t bits; // class
...
}
objc_class繼承于objc_object,objc中的類也是一個(gè)對(duì)象晓殊。
objc-private.h
struct objc_object {
private:
isa_t isa;//objc_object唯一成員變量
public:
Class ISA();
Class getIsa();
void initIsa(Class cls /*nonpointer=false*/);
...
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
...
}
objc_object是objc中對(duì)象的定義断凶。isa 是 objc_object的唯一成員變量。我們經(jīng)常說巫俺,所有的objc對(duì)象都包含一個(gè)isa指針认烁,從源碼上來看,現(xiàn)在準(zhǔn)確說應(yīng)該是isa_t結(jié)構(gòu)體(isa指針應(yīng)該是isa_t聯(lián)合體中的Class cls
指針)介汹。當(dāng)objc為一個(gè)對(duì)象分配內(nèi)存却嗡,初始化實(shí)例變量后,在這些對(duì)象的實(shí)例變量的結(jié)構(gòu)體中的第一個(gè)就是isa嘹承。
為了方便閱讀窗价,我把objc_class寫成:
struct objc_class : objc_object {
isa_t isa;
Class superclass; //父類的指針
cache_t cache; // formerly cache pointer and vtable 方法緩存
class_data_bits_t bits; // class
...
}
在objc中,對(duì)象的方法的實(shí)現(xiàn)不會(huì)存儲(chǔ)在每個(gè)對(duì)象的結(jié)構(gòu)體中叹卷,而是在相應(yīng)的類里(如果每一個(gè)對(duì)象都要維護(hù)一個(gè)實(shí)例方法列表撼港,那么開銷太大了)。當(dāng)一個(gè)實(shí)例方法被調(diào)用時(shí)骤竹,會(huì)通過對(duì)象的isa帝牡,在對(duì)應(yīng)的類中找到方法的實(shí)現(xiàn)(具體是在class_data_bits_t結(jié)構(gòu)體中查找,里面有一個(gè)方法列表)蒙揣。
同時(shí)我們還從源碼中看到靶溜,objc_class結(jié)構(gòu)體中還有一個(gè)Class類型的superclass成員變量,指向了父類懒震。通過這個(gè)指針可以查找從父類繼承的方法罩息。
然而,對(duì)于類對(duì)象來說个扰,它的isa又是什么呢瓷炮?objective-c里面有一種叫做meta class元類的東西。
為了讓我們能夠調(diào)用類方法,類的isa“指針”必須指向一個(gè)類結(jié)構(gòu)恐锣,并且該類結(jié)構(gòu)必須包含我們可以調(diào)用的類方法列表。這就導(dǎo)致了元類的定義:元類是類對(duì)象的類舞痰。
類方法調(diào)用時(shí)土榴,通過類的isa“指針”在元類中獲取方法的實(shí)現(xiàn)。元類中存儲(chǔ)了一個(gè)類的所有類方法响牛。
從上圖中總結(jié)以下幾個(gè)信息:
- root class(class)就是NSObject玷禽,由于它是基類赫段,所以它沒有父類。
- 實(shí)例對(duì)象的isa指向其類矢赁,類的isa指向其元類糯笙。每個(gè)元類的isa都指向根元類root class(meta),根元類的isa指向自己撩银。
- 根元類的父類指針指向基類(NSObject)给涕。
What is a meta-class in Objective-C?
ps:isa指針并不總是指向?qū)嵗龑?duì)象所屬的類,不能依靠它來確定類型额获,而應(yīng)用class方法够庙。(KVO的實(shí)現(xiàn)中,將被觀察的對(duì)象的isa指針指向一個(gè)中間類而非真實(shí)的類)抄邀。
2.isa_t結(jié)構(gòu)體的分析
通過源碼耘眨,我們可以知道isa_t實(shí)際上是一個(gè)union聯(lián)合體。其中的方法境肾、成員變量剔难、結(jié)構(gòu)體公用一塊空間。取決于其中的結(jié)構(gòu)體奥喻,最終isa_t共占64位內(nèi)存空間
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;//指針
uintptr_t bits;
#if SUPPORT_PACKED_ISA
//-------------------arm64上的實(shí)現(xiàn)
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL//ISA_MAGIC_MASK 和 ISA_MASK 分別是通過掩碼的方式獲取MAGIC值 和 isa類指針
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; //0:普通isa指針偶宫,1:優(yōu)化的指針用于存儲(chǔ)引用計(jì)數(shù)
uintptr_t has_assoc : 1; //表示該對(duì)象是否包含associated object,如果沒有衫嵌,析構(gòu)時(shí)會(huì)更快
uintptr_t has_cxx_dtor : 1; //表示對(duì)象是否含有c++或者ARC的析構(gòu)函數(shù)读宙,如果沒有,析構(gòu)更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 類的指針
uintptr_t magic : 6; //用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化(用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間)
uintptr_t weakly_referenced : 1; //對(duì)象是否有過weak對(duì)象
uintptr_t deallocating : 1; //是否正在析構(gòu)
uintptr_t has_sidetable_rc : 1; //該對(duì)象的引用計(jì)數(shù)值是否過大無法存儲(chǔ)在isa指針
uintptr_t extra_rc : 19; //存儲(chǔ)引用計(jì)數(shù)值減一后的結(jié)果楔绞。對(duì)象的引用計(jì)數(shù)超過 1结闸,會(huì)存在這個(gè)這個(gè)里面,如果引用計(jì)數(shù)為 10酒朵,extra_rc的值就為 9桦锄。
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
//-------------------__x86_64__上的實(shí)現(xiàn)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6; //值為0x3b
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
#endif
...略
由于源碼要在mac os下才能編譯,因此接下來都基于x86_64分析蔫耽,arm64其實(shí)大同小異结耀。
源碼中遇到的編譯宏的說明:
SUPPORT_PACKED_ISA
:表示平臺(tái)是否支持在 isa 指針中插入除 Class 之外的信息。如果支持就會(huì)將 Class 信息放入 isa_t 定義的 struct 內(nèi)(shiftcls)匙铡,并附上一些其他信息图甜,比如引用計(jì)數(shù),析構(gòu)狀態(tài)鳖眼;如果不支持黑毅,那么不會(huì)使用 isa_t 內(nèi)定義的 struct,這時(shí) isa_t 只使用 cls成員變量(Class 指針钦讳,經(jīng)常說的“isa指針”就是這個(gè))矿瘦。在 iOS 以及 MacOSX 上枕面,SUPPORT_PACKED_ISA 定義為 1(支持)。
struct結(jié)構(gòu)體的成員含義:
參數(shù) 含義
nonpointer 0 表示普通的 isa 指針缚去,1 表示使用優(yōu)化潮秘,存儲(chǔ)引用計(jì)數(shù)
has_assoc 表示該對(duì)象是否包含 associated object 關(guān)聯(lián)對(duì)象,如果沒有易结,則析構(gòu)時(shí)會(huì)更快
has_cxx_dtor 表示該對(duì)象是否有 C++ 或 ARC 的析構(gòu)函數(shù)枕荞,如果沒有,則析構(gòu)時(shí)更快
shiftcls 類的指針
magic __x86_64__環(huán)境初始化值為 0x3b衬衬,用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化买猖。
weakly_referenced 表示該對(duì)象是否有過 weak 對(duì)象,如果沒有滋尉,則析構(gòu)時(shí)更快
deallocating 表示該對(duì)象是否正在析構(gòu)
has_sidetable_rc 表示該對(duì)象的引用計(jì)數(shù)值是否過大無法存儲(chǔ)在 isa 指針
extra_rc 存儲(chǔ)引用計(jì)數(shù)值減一后的結(jié)果玉控。19位將保存對(duì)象的引用計(jì)數(shù),這樣對(duì)引用計(jì)數(shù)的操作只需要原子的修改這個(gè)指針即可狮惜,如果引用計(jì)數(shù)超出19位高诺,才會(huì)將引用計(jì)數(shù)保存到外部表,而這種情況往往是很少的碾篡,因此效率將會(huì)大大提高虱而。
在初始化一節(jié)會(huì)對(duì)其中一些參數(shù)繼續(xù)談?wù)劇?/p>
初始化
在我們調(diào)用alloc來實(shí)例化對(duì)象時(shí),會(huì)調(diào)用到objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
方法开泽。方法的具體實(shí)現(xiàn):
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
//__x86_64__和arm64會(huì)進(jìn)入這個(gè)條件分支
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
initInstanceIsa
方法實(shí)際上調(diào)用了initIsa
方法牡拇,并且傳入nonpointer=true
參數(shù)。
由于isa_t是一個(gè)聯(lián)合體穆律,所以newisa.bits = ISA_MAGIC_VALUE;
把isa_t中的struct結(jié)構(gòu)體初始化為:
實(shí)際上只設(shè)置了nonpointer和magic的值惠呼。
nonpointer
nonpointer 表示是否對(duì)isa開啟指針優(yōu)化。
先介紹一種稱為Tagged Pointer的技術(shù)峦耘,這是蘋果為64位設(shè)備提出的節(jié)省內(nèi)存和提高執(zhí)行效率的一種優(yōu)化方案剔蹋。
假設(shè)我們要存儲(chǔ)一個(gè)NSNumber對(duì)象,其值是一個(gè)整數(shù)辅髓。正常情況下泣崩,如果這個(gè)整數(shù)只是一個(gè)NSInteger的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān)洛口,在32位CPU下占4個(gè)字節(jié)矫付,在64位CPU下是占8個(gè)字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān)第焰,一個(gè)指針?biāo)加玫膬?nèi)存在32位CPU下為4個(gè)字節(jié)技即,在64位CPU下也是8個(gè)字節(jié)。
如果沒有Tagged Pointer對(duì)象樟遣,從32位機(jī)器遷移到64位機(jī)器中后而叼,雖然邏輯沒有任何變化,但這種NSNumber豹悬、NSDate一類的對(duì)象所占用的內(nèi)存會(huì)翻倍葵陵。
為了改進(jìn)上面提到的內(nèi)存占用和效率問題,蘋果提出了Tagged Pointer對(duì)象瞻佛。由于NSNumber脱篙、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個(gè)字節(jié),拿整數(shù)來說伤柄,4個(gè)字節(jié)所能表示的有符號(hào)整數(shù)就可以達(dá)到20多億绊困。所以我們可以將一個(gè)對(duì)象的指針拆成兩部分,一部分直接保存數(shù)據(jù)适刀,另一部分作為特殊標(biāo)記秤朗,表示這是一個(gè)特別的指針,不指向任何一個(gè)地址笔喉。
技術(shù)詳細(xì)可以查看以下兩個(gè)鏈接取视。
雖然Tagged Pointer專門用來存儲(chǔ)小的對(duì)象,例如NSNumber和NSDate常挚。但在isa指針優(yōu)化上用到了tagged Pointer的概念作谭。
使用整個(gè)指針大小的內(nèi)存來存儲(chǔ)isa有些浪費(fèi),在arm64上運(yùn)行的iOS只用了33位(和結(jié)構(gòu)體中shiftcls的33無關(guān)奄毡,Mac OS用了47位)折欠,剩下的31位用于其他目的。
nonpointer = 0 吼过,raw isa锐秦,表示isa_t不使用struct結(jié)構(gòu)體,訪問對(duì)象的 isa 會(huì)直接返回 cls 指針那先,cls 會(huì)指向?qū)ο笏鶎俚念惖慕Y(jié)構(gòu)农猬。這是在 iPhone 遷移到 64 位系統(tǒng)之前時(shí) isa 的類型。
nonpointer = 1售淡,isa_t使用了struct結(jié)構(gòu)體斤葱,此時(shí) isa 不再是指針,不能直接訪問 objc_object 的 isa 成員變量揖闸,但是其中也有 cls 的信息揍堕,只是其中關(guān)于類的指針都是保存在 shiftcls 中。
bool hasNonpointerIsa();
這個(gè)函數(shù)就是用來判斷當(dāng)前對(duì)象的isa是否啟用tagged pointer的汤纸。
magic
用于判斷當(dāng)前對(duì)象是否已經(jīng)完成初始化衩茸。
has_cxx_dtor
接著設(shè)置has_cxx_dtor,這一位表示當(dāng)前對(duì)象是否有 C++ 或者 ObjC 的析構(gòu)器贮泞,如果沒有析構(gòu)器就會(huì)快速釋放內(nèi)存楞慈。
shiftcls
newisa.shiftcls = (uintptr_t)cls >> 3;
將對(duì)象對(duì)應(yīng)的類的指針存入結(jié)構(gòu)體的shiftcls成員中幔烛。
將cls(地址)右移三位的原因:字節(jié)對(duì)齊。類的指針按照8字節(jié)對(duì)齊囊蓝。
對(duì)象的內(nèi)存地址必須對(duì)齊到字節(jié)的倍數(shù)饿悬,這樣可以提高代碼運(yùn)行的性能,在 iPhone5s 中虛擬地址為 33 位聚霜,所以用于對(duì)齊的最后三位比特為 000狡恬,我們只會(huì)用其中的 30 位來表示對(duì)象的地址。
將cls右移三位蝎宇,可以將地址中無用的后三位清除減小內(nèi)存的消耗弟劲。
回過頭來看,類的isa的shiftcls就指向元類了姥芥。
ISA()
由于開啟了指針優(yōu)化后兔乞,isa不再是指針,要獲取類指針就要用到 ISA() 方法撇眯。
#define ISA_MASK 0x00007ffffffffff8ULL
inline Class
objc_object::ISA()
{
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK); //按位與運(yùn)算獲取類指針报嵌,__x86_64__是44位
#endif
}
3.cache_t 結(jié)構(gòu)體的分析
typedef unsigned int uint32_t;
typedef uint32_t mask_t;
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
......
};
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
......
};
cache 存放著實(shí)例方法的緩存,提高方法調(diào)用效率熊榛。當(dāng)一個(gè)對(duì)象調(diào)用一個(gè)方法時(shí)锚国,它會(huì)先從緩存里面找這個(gè)方法,如果沒有找到才會(huì)去類的方法列表中去找玄坦。
如果一個(gè)對(duì)象調(diào)用一個(gè)方法血筑,首先根據(jù)對(duì)象的isa找到對(duì)應(yīng)的類,再在類的方法列表中尋找這個(gè)方法煎楣,如果找不到就到父類中的方法列表查找豺总,一旦找到就調(diào)用。如果沒有找到择懂,有可能轉(zhuǎn)發(fā)消息喻喳,也可能忽略它。但這樣效率太低了困曙,有些方法會(huì)經(jīng)常用到表伦,那么每次調(diào)用都要走一遍以上流程,是不是很慢慷丽?用cache來緩存方法蹦哼,優(yōu)先在 cache 中查找,找到就調(diào)用沒找到再走正常路子要糊。
cache_t中存儲(chǔ)了一個(gè)bucket_t結(jié)構(gòu)體指針和兩個(gè)無符號(hào)整形變量纲熏。
bucket_t結(jié)構(gòu)體中存儲(chǔ)了IMP函數(shù)指針和unsigned long。_buckets 用來存儲(chǔ)Method的鏈表。
_mask 分配用來緩存bucket的總數(shù)局劲。
_occupied 當(dāng)前占用的bucket數(shù)勺拣。
4.class_data_bits_t
之前說過,實(shí)例方法被調(diào)用時(shí)容握,會(huì)通過其持有 isa 指針尋找對(duì)應(yīng)的類宣脉,然后在其中的 class_data_bits_t 中查找對(duì)應(yīng)的方法。相對(duì)來說 class_data_bits_t 會(huì)有點(diǎn)復(fù)雜剔氏,包含了 class_rw_t 、class_ro_t 等重要的結(jié)構(gòu)體竹祷。這一節(jié)會(huì)對(duì) class_data_bits_t 進(jìn)行分析谈跛,介紹方法是如何在objc中存取的。
struct class_data_bits_t {
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
......
};
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 這個(gè)類的實(shí)例方法鏈表
class_rw_t *data() {
return bits.data();
}
......
};
class_data_bits_t 結(jié)構(gòu)體只含有一個(gè)64bit的bits變量塑陵,它存儲(chǔ)了類相關(guān)的信息感憾,和不同的以'FAST_'開頭的flag掩碼做按位與運(yùn)算。
uintptr_t bits存儲(chǔ)了一個(gè)指向 class_rw_t 結(jié)構(gòu)體的指針和三個(gè)標(biāo)志位令花。bits在64位兼容版中的內(nèi)容:
// 當(dāng)前類是swift類
#define FAST_IS_SWIFT (1UL<<0)
//當(dāng)前類或者父類含有默認(rèn)的 retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference 方法
#define FAST_HAS_DEFAULT_RR (1UL<<1)
// 當(dāng)前類的實(shí)例需要 raw isa
#define FAST_REQUIRES_RAW_ISA (1UL<<2)
// 數(shù)據(jù)指針
#define FAST_DATA_MASK 0x00007ffffffffff8UL
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
在 objc_class 的 data 方法中阻桅,直接調(diào)用并返回了 class_data_bits_t 的data方法。bits 和 FAST_DATA_MASK做按位與運(yùn)算兼都,轉(zhuǎn)換為 class_rw_t *并返回嫂沉。
Mac OS X 只使用 47 位內(nèi)存地址,所以bits前 17 位空余出來扮碧。bits中最大的一塊存儲(chǔ)區(qū)域用于存儲(chǔ)class_rw_t 指針趟章,在 FAST_DATA_MASK 對(duì)應(yīng)的[3,46]位數(shù)據(jù)段中。[3,46]一共是44位慎王,有沒有覺得44很眼熟蚓土?由于字節(jié)對(duì)齊,所以47位的后三位為0赖淤,可以用來做標(biāo)志位蜀漆,所以就有了另外三個(gè)標(biāo)志位。
除了 FAST_DATA_MASK 是用一段空間做存儲(chǔ)外咱旱,其他宏都是用1bit确丢。這些數(shù)據(jù)都有對(duì)應(yīng)的getter、setter封裝函數(shù)莽龟。比如:
bool isSwift() {return getBit(FAST_IS_SWIFT);}
void setIsSwift() {setBits(FAST_IS_SWIFT);}
bool hasDefaultRR() {return getBit(FAST_HAS_DEFAULT_RR);}
void setHasDefaultRR() {setBits(FAST_HAS_DEFAULT_RR);}
在64非兼容版本下還有更多的宏定義蠕嫁,位于objc-runtime-new.h文件中,如有需要請(qǐng)自行閱讀源碼毯盈。
class_rw_t
從上面的分析中我們知道剃毒,class_data_bits_t 用了大段空間存儲(chǔ)了 class_rw_t 指針。class_rw_t 提供了運(yùn)行時(shí)對(duì)類擴(kuò)展的能力,class_rw_t 結(jié)構(gòu)體中保存了 objc 中的屬性赘阀、方法益缠、協(xié)議等信息。
在 objc_class結(jié)構(gòu)體中的注釋寫到 class_data_bits_t相當(dāng)于 class_rw_t指針加上 rr/alloc 的標(biāo)志基公。
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
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;
method_array_t methods; //方法
property_array_t properties;//屬性
protocol_array_t protocols;//協(xié)議
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;//是計(jì)算機(jī)語言用于解決實(shí)體名稱唯一性的一種方法幅慌,做法是向名稱中添加一些類型信息,用于從編譯器中向連接器傳遞更多語義信息轰豆。
......
};
關(guān)于32位 flags 標(biāo)志胰伍,在源碼中有這樣一段注釋:
Values for class_rw_t->flags
These are not emitted by the compiler and are never used in class_ro_t.
Their presence should be considered in future ABI versions.
它的值不是由編譯器設(shè)置的,并且從不在class_ro_t中使用酸休。未來的ABI版本應(yīng)考慮到他們的存在骂租。
flags 標(biāo)記了類的一些狀態(tài),與生命周期斑司、內(nèi)存管理有關(guān)渗饮,有些位目前還沒有定義。
class_rw_t 中的 methods宿刮、properties互站、protocols 存放了和類的方法、屬性僵缺、協(xié)議有關(guān)的信息胡桃。
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
class method_array_t : public list_array_tt<method_t, method_list_t>
class property_array_t : public list_array_tt<property_t, property_list_t>
class protocol_array_t : public list_array_tt<protocol_ref_t, protocol_list_t>
method_array_t、property_array_t谤饭、protocol_array_t這三種類都繼承自 list_array_tt<Element, List>這種結(jié)構(gòu)标捺。
template <typename Element, typename List>
class list_array_tt {
struct array_t {
uint32_t count;
List* lists[0];//長度為0的數(shù)組,c99的寫法,允許在運(yùn)行期動(dòng)態(tài)申請(qǐng)內(nèi)存
};
}
list_array_tt 存儲(chǔ)一些元數(shù)據(jù),是通過c++模板定義的容器類答毫,提供了一些諸如count年鸳、迭代器iterator的方法和類。Element表示元數(shù)據(jù)的類型,比如 method_t 、 property_t 、 protocol_ref_t屋谭;List表示存儲(chǔ)元數(shù)據(jù)的容器,理解成是用于存儲(chǔ)元數(shù)據(jù)的一維數(shù)組龟糕,比如 method_list_t 桐磁、 property_list_t 。由于list_array_tt 存儲(chǔ)了List指針數(shù)組讲岁,所以list_array_tt實(shí)際上可以看做是元數(shù)據(jù)的二維數(shù)組我擂。list_array_tt 有三種狀態(tài):
- 自身為空
- List指針數(shù)組只有一個(gè)指向元數(shù)據(jù)數(shù)組的指針
- List指針數(shù)組有多個(gè)指針
一個(gè)類創(chuàng)建之初可能處于前兩個(gè)狀態(tài)衬以,如果用category或者class_addMethod來添加方法,就變成第三個(gè)狀態(tài)校摩,而且是不可逆的回不去前兩個(gè)狀態(tài)
可以對(duì)list_array_tt不斷進(jìn)行擴(kuò)張看峻。比如在通過category添加方法時(shí),就調(diào)用到這個(gè)方法衙吩,把新的方法列表(相當(dāng)于是裝有一個(gè)category所有方法的容器)添加到二維數(shù)組中:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
//調(diào)用realloc將原空間擴(kuò)展互妓,把原數(shù)組復(fù)制到后面,新數(shù)組復(fù)制到前面
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
//List指針數(shù)組只有一個(gè)指針時(shí)
//malloc重新申請(qǐng)內(nèi)存坤塞,最后一個(gè)位置留給原來元數(shù)據(jù)數(shù)組的指針
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
無論是哪一種邏輯冯勉,新加的方法列表都會(huì)添加到二維數(shù)組前面。
有一個(gè)名字很類似的結(jié)構(gòu)體 class_ro_t尺锚,'rw' 和 ro' 相信很容易就理解是 'readwrite' 和 'readonly'的意思吧珠闰。
class_rw_t 的結(jié)構(gòu)體成員中還有一個(gè)指向常量的指針class_ro_t *ro
。因此在編譯期間類的結(jié)構(gòu)中的 class_data_bits_t *data 指向的是一個(gè) class_ro_t * 指針瘫辩。class_ro_t也是一個(gè)結(jié)構(gòu)體,下一節(jié)我們來講講它坛悉。
class_ro_t
class_rw_t 的內(nèi)容可以在運(yùn)行時(shí)動(dòng)態(tài)修改伐厌,而 class_ro_t 存儲(chǔ)了在類編譯時(shí)就確定的信息,比如屬性裸影、方法挣轨、協(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;//ivar列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;//屬性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
在類進(jìn)行初始化之前卷扮,通過object_class 的data()方法得到的實(shí)際是一個(gè)指向 class_ro_t 結(jié)構(gòu)體的指針。
instanceStart均践、instanceSize 兩個(gè)成員變量的存在保證了objc2.0的ABI穩(wěn)定性晤锹。Non Fragile ivars
method_list_t、ivar_list_t彤委、property_list_t 結(jié)構(gòu)體都繼承自 entsize_list_tt<Element, List, FlagMask> , protocol_list_t 的結(jié)構(gòu)則相對(duì)簡單很多鞭铆。entsize_list_tt 是通過c++模板定義的容器類,提供了一些諸如count焦影、get车遂、迭代器iterator的方法和類,通過這些方法和類可以方便地遍歷并獲取容器內(nèi)的數(shù)據(jù)斯辰。
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
uint32_t entsizeAndFlags;//總大小
uint32_t count;//個(gè)數(shù)
Element first;//第一個(gè)元數(shù)據(jù)
};
而在類進(jìn)行初始化之時(shí)舶担,會(huì)調(diào)用到一個(gè) static Class realizeClass(Class cls)
方法(這個(gè)之后分析oc中的消息發(fā)送時(shí)會(huì)具體分析,這里只要知道會(huì)調(diào)用到這個(gè)方法即可)彬呻。
realizeClass的實(shí)現(xiàn)節(jié)選:
ro = (const class_ro_t *)cls->data();//強(qiáng)制轉(zhuǎn)換
if (ro->flags & RO_FUTURE) {
// 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 {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);//初始化一個(gè) class_rw_t 結(jié)構(gòu)體,分配可讀寫數(shù)據(jù)空間
rw->ro = ro;//設(shè)置結(jié)構(gòu)體 ro 的值以及 flag
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);//最后設(shè)置正確的 data
}
這里主要做了幾個(gè)事:
- 調(diào)用 object_class 的 data 方法衣陶,把得到的 class_rw_t 指針強(qiáng)制轉(zhuǎn)換成 class_ro_t 指針柄瑰。
- 新初始化一個(gè) class_rw_t ,分配可讀寫數(shù)據(jù)空間祖搓。
- 將 class_ro_t 指針賦值給 class_rw_t->ro 狱意,并設(shè)置 class_rw_t->flag 。
- 設(shè)置 object_class 的正確 data 拯欧。
也即由以下圖1變成了圖2:圖片來源深入解析 ObjC 中方法的結(jié)構(gòu)
但是此時(shí) class_rw_t 中的方法详囤,屬性以及協(xié)議列表均為空。最后在 realizeClass 方法中 調(diào)用了 methodizeClass 方法來將類 class_ro_t 的方法镐作、屬性和遵循的協(xié)議加載到 methods藏姐、 properties 和 protocols 列表中(用到了在 class_rw_t 一節(jié)最后提到的 attachLists 方法),但是class_ro_t其中的 ivar 不會(huì)被拷貝(不能在運(yùn)行時(shí)給已有的類增加實(shí)例變量)该贾。methodizeClass 方法節(jié)選:
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
可以這樣說羔杨,經(jīng)過realizeClass
函數(shù)處理的類才成為了真正的類。
5.驗(yàn)證一下
一下這part的內(nèi)容基于Draveness大大《深入解析 ObjC 中方法的結(jié)構(gòu)》一文中驗(yàn)證部分進(jìn)行復(fù)現(xiàn)杨蛋,分析運(yùn)行時(shí)初始化過程的內(nèi)存變化兜材,作為自己敲一下代碼所做的記錄。
#import <Foundation/Foundation.h>
@interface XXObject : NSObject
@property (nonatomic, assign) CGFloat test;
- (void)hello;
@end
#import "XXObject.h"
@implementation XXObject
- (void)hello {
NSLog(@"Hello");
}
@end
主程序代碼:
#import <Foundation/Foundation.h>
#import "XXObject.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class cls = [XXObject class];
NSLog(@"%p", cls);
}
return 0;
}
由于類在內(nèi)存中的位置是編譯期就確定的逞力,在之后修改代碼曙寡,也不會(huì)改變內(nèi)存中的位置。所以先跑一次代碼獲取XXObject 在內(nèi)存中的地址寇荧。地址為:0x1000011e0
在運(yùn)行時(shí)初始化之前加入斷點(diǎn):
然后通過lldb驗(yàn)證一下:
(lldb) p (objc_class *)0x1000011e0
(objc_class *) $0 = 0x00000001000011e0
(lldb) p (class_data_bits_t *)0x100001200 //偏移32為獲取class_data_bits_t指針
(class_data_bits_t *) $1 = 0x0000000100001200
(lldb) p $1->data()
warning: could not find Objective-C class data in the process. This may reduce the quality of type information available.
(class_rw_t *) $2 = 0x0000000100001158
(lldb) p (class_ro_t *)$2 //強(qiáng)制轉(zhuǎn)換類型
(class_ro_t *) $3 = 0x0000000100001158
(lldb) p *$3
(class_ro_t) $4 = {
flags = 128
instanceStart = 8
instanceSize = 16
reserved = 0
ivarLayout = 0x0000000000000000 <no value available>
name = 0x0000000100000f6e "XXObject"
baseMethodList = 0x00000001000010c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001118
weakIvarLayout = 0x0000000000000000 <no value available>
baseProperties = 0x0000000100001140
}
可以看到name举庶、baseMethodList、ivars揩抡、baseProperties是有值的户侥,對(duì)于后兩者有值的原因:因?yàn)閄XObject類中我添加了@property (nonatomic, assign) CGFloat test;
屬性,而屬性也在編譯期自動(dòng)生成了一個(gè)對(duì)應(yīng)的ivar(帶下劃線前綴的原屬性名的成員變量)峦嗤。
//獲取ivars
(lldb) p $4.ivars
(const ivar_list_t *) $6 = 0x0000000100001118
(lldb) p *$6
//顯示count=1只有一個(gè)ivar
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 1
first = {
offset = 0x00000001000011b0
name = 0x0000000100000f8b "_test"
type = 0x0000000100000fb2 "d"
alignment_raw = 3
size = 8
}
}
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x00000001000011b0
name = 0x0000000100000f8b "_test"
type = 0x0000000100000fb2 "d"
alignment_raw = 3
size = 8
}
//獲取property
(lldb) p $4.baseProperties
(property_list_t *) $10 = 0x0000000100001140
(lldb) p *$10
(property_list_t) $11 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "test", attributes = "Td,N,V_test")
}
}
查看 baseMethodList 中的內(nèi)容:
(lldb) p $4.baseMethodList
(method_list_t *) $14 = 0x00000001000010c8
(lldb) p *$14
(method_list_t) $15 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 3
first = {
name = "hello"
types = 0x0000000100000f97 "v16@0:8"
imp = 0x0000000100000e20 (debug-objc`-[XXObject hello] at XXObject.m:11)
}
}
}
(lldb) p $15.get(0)
(method_t) $16 = {
name = "hello"
types = 0x0000000100000f97 "v16@0:8"
imp = 0x0000000100000e20 (debug-objc`-[XXObject hello] at XXObject.m:11)
}
(lldb) p $15.get(1)
(method_t) $17 = {
name = "test"
types = 0x0000000100000f9f "d16@0:8"
imp = 0x0000000100000e50 (debug-objc`-[XXObject test] at XXObject.h:11)
}
(lldb) p $15.get(2)
(method_t) $18 = {
name = "setTest:"
types = 0x0000000100000fa7 "v24@0:8d16"
imp = 0x0000000100000e70 (debug-objc`-[XXObject setTest:] at XXObject.h:11)
}
結(jié)果顯示 baseMethodList 中有三個(gè)方法蕊唐,打印出來看,第一個(gè)是 XXObject 的 hello 方法寻仗,其余兩個(gè)是 test 屬性的對(duì)應(yīng)getter和setter方法刃泌。所以也就不難理解為什么objc中的property = ivar + getter + setter了。
關(guān)于"v16@0:8"
署尤、"d16@0:8"
耙替、"v24@0:8d16"
請(qǐng)見Type Encoding
以上這些都是在編譯器就生成了的。
接著來看一下如果沒有把class_rw_t強(qiáng)轉(zhuǎn)成class_ro_t時(shí)的情形:先打印出來留到后面與初始化之后進(jìn)行對(duì)比曹体。
(lldb) p *$2
(class_rw_t) $5 = {
flags = 128
version = 8
ro = 0x0000000000000010
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100000f6e
arrayAndFlag = 4294971246
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x00000001000010c8
arrayAndFlag = 4294971592
}
}
}
firstSubclass = nil
nextSiblingClass = 0x0000000100001118
demangledName = 0x0000000000000000 <no value available>
}
接著在 realizeClass 方法中的這個(gè)地方打一個(gè)斷點(diǎn):(直接和地址值進(jìn)行比較是因?yàn)轭愒诰幾g期就確定了在內(nèi)存中的地址)
判斷當(dāng)前類是否XXObject俗扇,此時(shí)還沒有進(jìn)行初始化,類結(jié)構(gòu)體中的布局依舊是上面那樣沒有變化的箕别。
[圖片上傳失敗...(image-5a0ad-1517664314598)]
當(dāng)這段代碼運(yùn)行完后铜幽,再來lldb打印一下:
(lldb) p (objc_class *)0x1000011e0
(objc_class *) $19 = 0x00000001000011e0
(lldb) p (class_data_bits_t *)0x0000000100001200
(class_data_bits_t *) $20 = 0x0000000100001200
(lldb) p $20->data()
(class_rw_t *) $21 = 0x0000000100f05030
(lldb) p *$21
(class_rw_t) $22 = {
flags = 2148007936
version = 0
ro = 0x0000000100001158
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = nil
demangledName = 0x0000000000000000 <no value available>
}
(lldb) p $22.ro
(const class_ro_t *) $23 = 0x0000000100001158
(lldb) p *$23
(const class_ro_t) $24 = {
flags = 128
instanceStart = 8
instanceSize = 16
reserved = 0
ivarLayout = 0x0000000000000000 <no value available>
name = 0x0000000100000f6e "XXObject"
baseMethodList = 0x00000001000010c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001118
weakIvarLayout = 0x0000000000000000 <no value available>
baseProperties = 0x0000000100001140
}
可以看到滞谢,$3
的class_ro_t *
已經(jīng)被設(shè)置成$22
class_rw_t中的 ro 成員了,但 class_rw_t 的 methods除抛、properties狮杨、protocols 成員
仍然為空。
等到執(zhí)行完 methodizeClass 方法:
(lldb) p *$21
(class_rw_t) $25 = {
flags = 2148007936
version = 0
ro = 0x0000000100001158
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010c8
arrayAndFlag = 4294971592
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100001140
arrayAndFlag = 4294971712
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fffcd723f50
demangledName = 0x0000000000000000 <no value available>
}
class_ro_t 中 baseMethodList 中的方法被添加到 class_rw_t 中的 methods 數(shù)組中到忽,class_ro_t 中 baseProperties 被添加到 class_rw_t 中的 properties橄教。核心都是通過在 methodizeClass 方法中執(zhí)行 attachLists 方法添加類的方法、屬性喘漏、協(xié)議:
auto rw = cls->data();
auto ro = rw->ro;
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
總結(jié)
類的方法护蝶、屬性以及協(xié)議在編譯期間存放到了“錯(cuò)誤”的位置,直到 realizeClass 執(zhí)行之后翩迈,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t持灰,這樣我們即可以在運(yùn)行時(shí)為 class_rw_t 添加方法,也不會(huì)影響類的只讀結(jié)構(gòu)负饲。
在 class_ro_t 中的屬性在運(yùn)行期間就不能改變了堤魁,再添加方法時(shí),會(huì)修改 class_rw_t 中的 methods 列表返十,而不是 class_ro_t 中的 baseMethodList
6.對(duì)象的初始化
創(chuàng)建一個(gè) NSObject 對(duì)象的代碼:[[NSObject alloc] init];
alloc
方法的實(shí)現(xiàn):
+ (id)alloc {
return _objc_rootAlloc(self);
}
僅僅調(diào)用了一個(gè)私有方法_objc_rootAlloc
:
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
// 檢查 cls 信息是否為 nil姨涡,如果為 nil,則無法創(chuàng)建新對(duì)象吧慢,返回 nil。
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
//是否有自定義的allocWithZone方法
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
//沒有alloc / allocWithZone實(shí)現(xiàn)赏表。
//是否可以快速分配內(nèi)存检诗。這里好像是寫死了返回false
if (fastpath(cls->canAllocFast())) {
bool dtor = cls->hasCxxDtor();//是否有析構(gòu)器
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
id obj = class_createInstance(cls, 0);// 創(chuàng)建對(duì)象的關(guān)鍵函數(shù)
if (slowpath(!obj)) return callBadAllocHandler(cls);// 分配失敗
return obj;
}
}
#endif
// callAlloc 傳入 allocWithZone = true
if (allocWithZone) return [cls allocWithZone:nil];// 這里 cls 的 allocWithZone 方法里也是調(diào)用了 class_createInstance。
return [cls alloc];
}
關(guān)于slowpath
瓢剿、fastpath
兩個(gè)宏逢慌,實(shí)際上是__builtin_expect(),一個(gè)GCC的一個(gè)內(nèi)建函數(shù)间狂,用于分支預(yù)測(cè)優(yōu)化攻泼。__builtin_expect — 分支預(yù)測(cè)優(yōu)化。只需要知道if(fastpath(x))
與 if(x)
是相同意思就可以了鉴象。
實(shí)際上我在執(zhí)行[[XXObject alloc] init]
這句代碼時(shí)忙菠,if (fastpath(!cls->ISA()->hasCustomAWZ()))
并沒有進(jìn)入這個(gè)條件分支:
所以也即相當(dāng)于:
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
return [cls allocWithZone:nil];
}
但很神奇的是,接下來并沒有執(zhí)行allocWithZone
方法(打了斷點(diǎn)但無事發(fā)生)纺弊,這是一個(gè)讓我覺得很迷的地方牛欢。最后落入的是這兩個(gè)方法:
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();// 是否有構(gòu)造函數(shù)
bool hasCxxDtor = cls->hasCxxDtor();// 是否有析構(gòu)函數(shù)
bool fast = cls->canAllocNonpointer();// 是否使用原始 isa 格式
size_t size = cls->instanceSize(extraBytes);// 需要分配的空間大小,從 instanceSize 實(shí)現(xiàn)可以知道對(duì)象至少16字節(jié)
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {//跑代碼時(shí)會(huì)執(zhí)行這個(gè)分支
obj = (id)calloc(1, size);//分配空間
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);//看到了一個(gè)熟悉的方法~
}
else { //這里不執(zhí)行
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
在上面函數(shù)的實(shí)現(xiàn)中淆游,執(zhí)行了 objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
方法傍睹。在第二部分講過隔盛,這個(gè)方法就是用來初始化isa_t結(jié)構(gòu)體的。
初始化 isa 之后拾稳,[NSObject alloc]
的工作算是做完了吮炕,下面就是 init
相關(guān)邏輯:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
可以看到, init
實(shí)際上只是返回了當(dāng)前對(duì)象访得。
總結(jié)一下對(duì)象的初始化過程主要做了兩個(gè)事:1.分配需要的內(nèi)存空間 2.初始化isa_t結(jié)構(gòu)體龙亲。
參考文章: