關(guān)注倉(cāng)庫(kù),及時(shí)獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github
在之前遗契,我們已經(jīng)討論了非常多的問題了溶弟,關(guān)于 objc 源代碼系列的文章也快結(jié)束了,其實(shí)關(guān)于對(duì)象是如何初始化的這篇文章本來是我要寫的第一篇文章胖翰,但是由于有很多前置內(nèi)容不得不說臼节,所以留到了這里撬陵。
+ alloc
和 - init
這一對(duì)我們?cè)?iOS 開發(fā)中每天都要用到的初始化方法一直困擾著我, 于是筆者仔細(xì)研究了一下 objc 源碼中 NSObject
如何進(jìn)行初始化。
在具體分析對(duì)象的初始化過程之前网缝,我想先放出結(jié)論巨税,以免文章中的細(xì)枝末節(jié)對(duì)讀者的理解有所影響;整個(gè)對(duì)象的初始化過程其實(shí)只是為一個(gè)分配內(nèi)存空間粉臊,并且初始化 isa_t 結(jié)構(gòu)體的過程草添。
alloc 方法分析
先來看一下 + alloc
方法的調(diào)用棧(在調(diào)用棧中省略了很多不必要的方法的調(diào)用):open
id _objc_rootAlloc(Class cls)
└── static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
└── id class_createInstance(Class cls, size_t extraBytes)
└── id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct, size_t *outAllocatedSize)
├── size_t instanceSize(size_t extraBytes)
├── void *calloc(size_t, size_t)
└── inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
這個(gè)調(diào)用棧中的方法涉及了多個(gè)文件中的代碼,在下面的章節(jié)中會(huì)對(duì)調(diào)用的方法逐步進(jìn)行分析扼仲,如果這個(gè)調(diào)用棧讓你覺得很頭疼远寸,也不是什么問題促王。
alloc 的實(shí)現(xiàn)
+ (id)alloc {
return _objc_rootAlloc(self);
}
alloc
方法的實(shí)現(xiàn)真的是非常的簡(jiǎn)單, 它直接調(diào)用了另一個(gè)私有方法 id _objc_rootAlloc(Class cls)
id _objc_rootAlloc(Class cls) {
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
這就是上帝類 NSObject
對(duì) callAlloc
的實(shí)現(xiàn),我們省略了非常多的代碼而晒,展示了最常見的執(zhí)行路徑:
static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
id obj = class_createInstance(cls, 0);
return obj;
}
id class_createInstance(Class cls, size_t extraBytes) {
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
對(duì)象初始化中最重要的操作都在 _class_createInstanceFromZone
方法中執(zhí)行:
static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {
size_t size = cls->instanceSize(extraBytes);
id obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
對(duì)象的大小
在使用 calloc
為對(duì)象分配一塊內(nèi)存空間之前蝇狼,我們要先獲取對(duì)象在內(nèi)存的大小:
size_t instanceSize(size_t extraBytes) {
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;
}
實(shí)例大小 instanceSize
會(huì)存儲(chǔ)在類的 isa_t
結(jié)構(gòu)體中倡怎,然后經(jīng)過對(duì)齊最后返回迅耘。
Core Foundation 需要所有的對(duì)象的大小都必須大于或等于 16 字節(jié)。
在獲取對(duì)象大小之后监署,直接調(diào)用 calloc
函數(shù)就可以為對(duì)象分配內(nèi)存空間了颤专。
isa 的初始化
在對(duì)象的初始化過程中除了使用 calloc
來分配內(nèi)存之外,還需要根據(jù)類初始化 isa_t
結(jié)構(gòu)體:
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;
}
}
上面的代碼只是對(duì) isa_t
結(jié)構(gòu)體進(jìn)行初始化而已:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
};
};
在這里并不想過多介紹關(guān)于
isa_t
結(jié)構(gòu)體的內(nèi)容钠乏,你可以看從 NSObject 的初始化了解 isa 來了解你想知道的關(guān)于isa_t
的全部?jī)?nèi)容栖秕。
init 方法
NSObject
的 - init
方法只是調(diào)用了 _objc_rootInit
并返回了當(dāng)前對(duì)象:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj) {
return obj;
}
總結(jié)
在 iOS 中一個(gè)對(duì)象的初始化過程很符合直覺,只是分配內(nèi)存空間晓避、然后初始化 isa_t
結(jié)構(gòu)體簇捍,其實(shí)現(xiàn)也并不復(fù)雜,這篇文章也是這個(gè)系列文章中較為簡(jiǎn)單并且簡(jiǎn)短的一篇俏拱。
關(guān)注倉(cāng)庫(kù)暑塑,及時(shí)獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github