以下源碼分析基于 objc4-781
對象的isa
初始化
在 +[NSObject alloc]
流程分析中淋硝,我們最終找到了對象創(chuàng)建的方法
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
在上面的方法中主要做了3件事:
-
size = cls->instanceSize(extraBytes)
計(jì)算對象占用內(nèi)存的大小鳄抒。 -
obj = (id)calloc(1, size)
創(chuàng)建對象。 -
obj->initInstanceIsa(cls, hasCxxDtor)
初始化對象的isa
谓谦。
id
類型
調(diào)用 calloc
方法將結(jié)果強(qiáng)轉(zhuǎn)成 id
類型芽世,在源碼中我們可以看到 id
類型的定義
// 定義在 Public Headers/objc.h
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
// 定義在 Project Headers/objc-private.h 中 line 82
struct objc_object {
isa_t isa;
}
/// A pointer to an instance of a class.
typedef struct objc_object *id;
我們可以在源碼中找到2個版本的 struct objc_object
定義今魔,一個在 Public Headers
目錄下私恬,另一個在 Project Headers
前者是暴露給開發(fā)者的,后者是內(nèi)部使用的柜裸,所以 id
類型本質(zhì)就是一個結(jié)構(gòu)體指針,struct objc_object
只有一個成員變量 isa_t isa
掷漱。
調(diào)用 calloc
方法粘室,只是從系統(tǒng)中申請了一片內(nèi)存空間,這個時(shí)候這塊內(nèi)存空間中沒有任何內(nèi)容卜范,通過 obj->initInstanceIsa
初始化對象中的 isa
。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
在 initInstanceIsa
方法中調(diào)用了 initIsa(cls, true, hasCxxDtor)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在 intiIsa
方法中鹿榜,主要就是對 isa
進(jìn)行初始化海雪。
isa_t
分析
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
union
聯(lián)合體
我們發(fā)現(xiàn) isa_t
是一個聯(lián)合體,它和結(jié)構(gòu)體有什么區(qū)別舱殿?
結(jié)構(gòu)體
結(jié)構(gòu)體是把不同的數(shù)據(jù)組合成一個整體奥裸,其變量是共存的,無論變量使用與否沪袭,都會分配內(nèi)存湾宙。
結(jié)構(gòu)體占用內(nèi)存為其所有成員占用內(nèi)存的總和。
- 優(yōu)點(diǎn):存儲容量較大冈绊,包容性強(qiáng)侠鳄,成員之間不會互相影響。
- 缺點(diǎn):所有屬性都會分配內(nèi)存死宣。
聯(lián)合體
聯(lián)合體也是由不同的數(shù)據(jù)類型組成的伟恶,但是其變量是互斥的,所有的成員占用同一段內(nèi)存毅该。而聯(lián)合體采用了內(nèi)存覆蓋技術(shù)博秫,同一時(shí)刻只能保存一個成員的值,如果對新的成員變量賦值眶掌,就會將原來的成員賦值挡育。
聯(lián)合體占用內(nèi)存為其最大成員占用的內(nèi)存。
- 優(yōu)點(diǎn):所有成員共用一段內(nèi)存朴爬,使內(nèi)存的使用更為精細(xì)靈活即寒,同事也節(jié)省了內(nèi)存空間。
- 缺點(diǎn):包容性弱寝殴。
在 union isa_t
結(jié)構(gòu)中蒿叠,有三個成員 Class cls
uintptr_t bits
和 struct { ISA_BITFIELD; }
他們占用內(nèi)存大小都是 8bytes
,因此整個聯(lián)合體占用內(nèi)存 8bytes
蚣常。
其中 struct { ISA_BITFIELD; }
是一個位域市咽,用來描述當(dāng)聯(lián)合體的值是 bits
是每一位所代表的意義
ISA_BITFIELD
是一段宏,展開后如上圖抵蚊。他有兩個版本施绎, __arm64__
表示真機(jī)上位域的信息溯革, __x86_64__
表示模擬器上的位域信息。
nonpointer
用來標(biāo)識 isa
是不是一個單純的 Class 指針
- 0:
isa
儲存一個 Class 指針地址谷醉。 - 1:
isa
不僅包含了 Class 指針地址致稀,還儲存了一下其他的信息。
has_assoc
用來標(biāo)識是不是又關(guān)聯(lián)對象
- 0:沒有關(guān)聯(lián)對象
- 1:存在關(guān)聯(lián)對象
has_cxx_dtor
標(biāo)識對象有沒有析構(gòu)器俱尼,如果沒有析構(gòu)函數(shù)抖单,可以更快的釋放
- 0:沒有析構(gòu)器
- 1:存在析構(gòu)器
shiftclx
用來儲存指針地址,在真機(jī)環(huán)境中最大的指針地址為 0x1000000000
遇八,轉(zhuǎn)換成二進(jìn)制為 1 0000 0000 0000 0000 0000 0000 0000 0000 0000
, 長度為37位矛绘, 且指針地址為8字節(jié)內(nèi)存對齊,由于內(nèi)存對齊的原因?qū)嶋H上 0x1000000000
也不是有效的地址刃永,低三位都為0货矮,所以指針的實(shí)際有效位數(shù)為33位。同樣的在模擬器中最大的指針地址為 0x7fffffe00000
對應(yīng)二進(jìn)制47位斯够, 除去低三位囚玫,有效的指針位數(shù)為44位。
magic
用于調(diào)試器判斷當(dāng)前對象是真正的對象读规,還是沒有初始化的空間抓督。
weakly_refrenced
表示對象是否指向或者曾經(jīng)指向一個ARC的弱引用變量。
deallocating
表示對象是否正在被釋放掖桦。
has_sidetable_rc
判斷該對象的引用對象是否過大本昏,如果過大則需要其他散列表來進(jìn)行存儲。
extra_rc
存放該對象的引用計(jì)數(shù)減1后的結(jié)果枪汪,如果引用計(jì)數(shù)超過1涌穆,會存在這個里面,如果引用計(jì)數(shù)為10雀久,extra_rc
的值就為9宿稀。