關注倉庫,及時獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github
在之前,我們已經討論了非常多的問題了,關於 objc 原始碼系列的文章也快結束了,其實關於物件是如何初始化的這篇文章本來是我要寫的第一篇文章,但是由於有很多前置內容不得不說,所以留到了這裡。
+ alloc
和 - init
這一對我們在 iOS 開發中每天都要用到的初始化方法一直困擾著我, 於是筆者仔細研究了一下 objc 原始碼中 NSObject
如何進行初始化。
在具體分析物件的初始化過程之前,我想先放出結論,以免文章中的細枝末節對讀者的理解有所影響;整個物件的初始化過程其實只是為一個分配記憶體空間,並且初始化 isa_t 結構體的過程。
alloc 方法分析
先來看一下 + alloc
方法的呼叫棧(在呼叫棧中省略了很多不必要的方法的呼叫):open
1 2 3 4 5 6 7 |
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) |
這個呼叫棧中的方法涉及了多個檔案中的程式碼,在下面的章節中會對呼叫的方法逐步進行分析,如果這個呼叫棧讓你覺得很頭疼,也不是什麼問題。
alloc 的實現
1 2 3 |
+ (id)alloc { return _objc_rootAlloc(self); } |
alloc
方法的實現真的是非常的簡單, 它直接呼叫了另一個私有方法 id _objc_rootAlloc(Class cls)
1 2 3 |
id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } |
這就是上帝類 NSObject
對 callAlloc
的實現,我們省略了非常多的程式碼,展示了最常見的執行路徑:
1 2 3 4 5 6 7 8 |
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); } |
物件初始化中最重要的操作都在 _class_createInstanceFromZone
方法中執行:
1 2 3 4 5 6 7 8 9 |
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; } |
物件的大小
在使用 calloc
為物件分配一塊記憶體空間之前,我們要先獲取物件在記憶體的大小:
1 2 3 4 |
size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; if (size ro->instanceSize; } |
例項大小 instanceSize
會儲存在類的 isa_t
結構體中,然後經過對齊最後返回。
Core Foundation 需要所有的物件的大小都必須大於或等於 16 位元組。
在獲取物件大小之後,直接呼叫 calloc
函式就可以為物件分配記憶體空間了。
isa 的初始化
在物件的初始化過程中除了使用 calloc
來分配記憶體之外,還需要根據類初始化 isa_t
結構體:
1 2 3 4 5 6 7 8 9 |
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; } } |
上面的程式碼只是對 isa_t
結構體進行初始化而已:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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; }; }; |
在這裡並不想過多介紹關於
isa_t
結構體的內容,你可以看從 NSObject 的初始化了解 isa 來了解你想知道的關於isa_t
的全部內容。
init 方法
NSObject
的 - init
方法只是呼叫了 _objc_rootInit
並返回了當前物件:
1 2 3 4 5 6 7 |
- (id)init { return _objc_rootInit(self); } id _objc_rootInit(id obj) { return obj; } |
總結
在 iOS 中一個物件的初始化過程很符合直覺,只是分配記憶體空間、然後初始化 isa_t
結構體,其實現也並不複雜,這篇文章也是這個系列文章中較為簡單並且簡短的一篇。
關注倉庫,及時獲得更新:iOS-Source-Code-Analyze
Follow: Draveness · Github