做爲 iOS
開發者,咱們天天打交道最多的應該就是對象了,從面向對象設計的角度來講,對象的建立以及初始化是最基礎的內容。那麼,今天咱們就一塊兒來探索一下 iOS
中最經常使用的 alloc
和 init
的底層是怎麼實現的吧。html
對於第三方開源框架來講,咱們去剖析內部原理和細節是有必定的方法和套路能夠掌握的。而對於 iOS
底層,特別是 OC
底層,咱們可能就須要用到一些開發中不是很經常使用的方法。程序員
咱們這個系列主要的目的是爲了進行底層探索,那麼咱們做爲 iOS
開發者,須要關注應該就是從應用啓動到應用被 kill
掉這一整個生命週期的內容。咱們不妨從咱們最熟悉的 main
函數開始,通常來講,咱們在 main.m
文件中打一個斷點,左側的調用堆棧視圖應該以下圖所示:macos
要獲得這樣的調用堆棧有兩個注意點:設計模式
- 須要關閉
Xcode
左側Debug
區域最下面的show only stack frames with debug symbols and between libraries
- 須要增長一個
_objc_init
的符號端點
咱們經過上面的調用堆棧信息不可貴出一個簡單粗略的加載流程結構sass
咱們如今心中創建這麼一個簡單的流程結構,在後期分析底層的時候咱們會回過頭來梳理整個啓動的流程。app
接下來,讓咱們開始實際的探索過程。框架
咱們直接打開 Xcode
新建一個 Single View App
工程,而後咱們在 ViewController.m
文件中調用 alloc
方法。ide
NSObject *p = [NSObject alloc];
咱們按照常規探索源碼的方式,直接按住 Command
+ Control
來進入到 alloc
內部實現,但結果並不是如咱們所願,咱們來到的是一個頭文件,只有 alloc
方法的聲明,並無對應的實現。這個時候,咱們會陷入深深的懷疑中,其實這個時候咱們只要記住下面三種經常使用探索方式就能迎刃而解:函數
具體操做方式爲 Control
+ in
post
這裏的 in
指的是左側圖片中紅色部分的按鈕,其實這裏的操做叫作 Step into instruction
。咱們能夠來到下圖這裏
咱們觀察不可貴出咱們想要找的就是 libobjc.A.dylib
這個動態連接庫了。
具體操做方式爲打開 Debug
菜單下的 Debug Workflow
下的 Always Show Disassembly
接着咱們仍是下代碼斷點,而後一步一步調試也會來到下圖這裏:
咱們先選擇 Symbolic Breakpoint
,而後輸入 objc_alloc
,以下圖所示:
至此,咱們獲得了 alloc
實現位於 libObjc
這個動態庫,而恰好蘋果已經開源了這部分的代碼,因此咱們能夠在 蘋果開源官網 最新版本 10.14.5 上下載便可。最新的 libObc
爲 756。
libObjc
源碼咱們下載了 libObjc
的源碼到咱們的電腦上後是不能直接運行的,咱們須要進行必定的配置才能實現源碼追蹤流程。這一塊內容不在本文範圍內,讀者可參考 iOS_objc4-756.2 最新源碼編譯調試。
配置好 libObjc
以後,咱們新建一個命令行的項目,而後運行以下代碼:
NSObject *myObj = [NSObject alloc];
而後咱們直接下符號斷點 objc_alloc
,而後一步步調試,先來到的是 objc_alloc
// Calls [cls alloc]. id objc_alloc(Class cls) { return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/); }
而後會來到 callAlloc
方法,注意這裏第三個參數傳的是 false
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { // 判斷傳入的 checkNil 是否進行判空操做 if (slowpath(checkNil && !cls)) return nil; // 若是當前編譯環境爲 OC 2.0 #if __OBJC2__ // 當前類沒有自定義的 allocWithZone if (fastpath(!cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // 既沒有實現 alloc,也沒有實現 allocWithZone 就會來到這裏,下面直接進行內存開闢操做。 // fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary // 修復沒有元類的類,用人話說就是沒有繼承於 NSObject // 判斷當前類是否能夠快速開闢內存,注意,這裏永遠不會被調用,由於 canAllocFast 內部 // 返回的是false if (fastpath(cls->canAllocFast())) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(!obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; }
由於咱們在 objc_init
中傳入的第三個參數 allocWithZone
是 true
,而且咱們的 cls
爲 NSObject
,那麼也就是說會這裏直接來到 return [cls alloc]
。咱們接着往下走會來到 alloc
方法:
+ (id)alloc { return _objc_rootAlloc(self); }
而後咱們接着進入 _objc_rootAlloc
方法內部:
// Base class implementation of +alloc. cls is not nil. // Calls [cls allocWithZone:nil]. id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); }
是否是有點似曾類似,沒錯,咱們第一步進入的 objc_init
也是調用的 callAlloc
方法,可是這裏有兩個參數是不同的,第二個參數 checkNil
是否須要判空直接傳的是 false
,站在系統角度,前面已經在第一次調用 callAlloc
的時候進行了判空了,因此這裏不必再次進行判空的了。第三個參數 allocWithZone
傳的是 true
,關於這個方法,我查閱了蘋果開發者文檔,文檔解釋以下:
Do not overrideallocWithZone:
to include any initialization code. Instead, class-specific versions ofinit...
methods.
This method exists for historical reasons; memory zones are no longer used by Objective-C.
譯:不要去重載allocWithZone
並在其內部填充任何初始化代碼,相反的,應該在init...
裏面進行類的初始化操做。
這個方法的存在是有歷史緣由的,內存zone
已經再也不被Objective-C
所使用的。
按照蘋果開發者文檔的說法,其實 allocWithZone
本質上和 alloc
是沒有區別的,只是在 Objective-C
遠古時代,程序員須要使用諸如 allocWithZone
來優化對象的內存結構,而在當下,其實你寫 alloc
和 allocWithZone
在底層是一模模同樣樣的。
好的,話題扯遠了,咱們接着再次進入到 callAlloc
方法內部,第二次來到 callAlloc
的話,在 !cls->ISA()->hasCustomAWZ()
這裏判斷 cls
沒有自定義的 allocWithZone
實現,這裏的判斷實質上是對 cls
也就是 object_class
這一結構體內部的 class_rw_t
的 flags
與上一個宏 RW_HAS_DEFAULT_AWZ
。通過筆者測試,在第一次進入 callAlloc
方法內部的時候, flags
值爲 1 ,而後 flags
與上 1<<16
結果就是 0 ,返回過去也就是 false
,而後在 hasCustomAWZ
這裏取反以後,返回的就是 true
,而後再一取反,天然就會跳過 if
裏面的邏輯;而第二次進入 callAlloc
方法內部的時候, flags
值是一個很大的整數,與上 1<<16
後結果並不爲0 ,因此 hasDefaultAWZ
會返回 true
,那麼 hasCustomAWZ
這裏就會返回 false
,那麼返回到 callAlloc
的時候天然就會進入 if
裏面的邏輯了。
這裏插一句,在咱們 OC 的類的結構中,有一個結構叫class_rw_t
,有一個結構叫class_ro_t
。其中class_rw_t
是能夠在運行時去拓展類的,包括屬性,方法、協議等等,而class_ro_t
則存儲了成員變量,屬性和方法等,不過這些是在編譯時就肯定了的,不能在運行時去修改。
bool hasCustomAWZ() { return ! bits.hasDefaultAWZ(); } bool hasDefaultAWZ() { return data()->flags & RW_HAS_DEFAULT_AWZ; }
而後咱們會來到 canAllocFast
的判斷,咱們繼續進入該方法內部
if (fastpath(cls->canAllocFast()))
bool canAllocFast() { assert(!isFuture()); return bits.canAllocFast(); } bool canAllocFast() { return false; }
結果很顯然,這裏 canAllocFast
是一直返回 false
的,也就是說會直接來到下面的邏輯
id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj;
咱們再次進入 class_createInstance
方法內部
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) { // 對 cls 進行判空操做 if (!cls) return nil; // 斷言 cls 是否實現了 assert(cls->isRealized()); // Read class's info bits all at once for performance // cls 是否有 C++ 的初始化構造器 bool hasCxxCtor = cls->hasCxxCtor(); // cls 是否有 C++ 的析構器 bool hasCxxDtor = cls->hasCxxDtor(); // cls 是否能夠分配 Nonpointer,若是是,即表明開啓了內存優化 bool fast = cls->canAllocNonpointer(); // 這裏傳入的 extraBytes 爲0,而後獲取 cls 的實例內存大小 size_t size = cls->instanceSize(extraBytes); // 這裏 outAllocatedSize 是默認值 nil,跳過 if (outAllocatedSize) *outAllocatedSize = size; id obj; // 這裏 zone 傳入的也是nil,而 fast 拿到的是 true,因此會進入這裏的邏輯 if (!zone && fast) { // 根據 size 開闢內存 obj = (id)calloc(1, size); // 若是開闢失敗,返回 nil if (!obj) return nil; // 將 cls 和是否有 C++ 析構器傳入給 initInstanceIsa,實例化 isa obj->initInstanceIsa(cls, hasCxxDtor); } else { // 若是 zone 不爲空,通過筆者測試,通常來講調用 alloc 不會來到這裏,只有 allocWithZone // 或 copyWithZone 會來到下面的邏輯 if (zone) { // 根據給定的 zone 和 size 開闢內存 obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { // 根據 size 開闢內存 obj = (id)calloc(1, size); } // 若是開闢失敗,返回 nil if (!obj) return nil; // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. // 初始化 isa obj->initIsa(cls); } // 若是有 C++ 初始化構造器和析構器,進行優化加速整個流程 if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } // 返回最終的結果 return obj; }
至此,咱們的 alloc
流程就探索完畢,但在這其中咱們仍是有一些疑問點,好比,對象的內存大小時怎麼肯定出來的, isa
是怎麼初始化出來的呢,不要緊,咱們下一篇接着探索。這裏,先給出筆者本身畫的一個 alloc
流程圖,限於筆者水平有限,有錯誤之處望讀者指出:
分析完了 alloc
的流程,咱們接着分析 init
的流程。相比於 alloc
來講, init
內部實現十分簡單,先來到的是 _objc_rootInit
,而後就直接返回 obj
了。其實這裏是一種抽象工廠設計模式的體現,對於 NSObject
自帶的 init
方法來講,其實啥也沒幹,可是若是你繼承於 NSObject
的話,而後就能夠去重寫 initWithXXX
之類的初始化方法來作一些初始化操做。
- (id)init { return _objc_rootInit(self); } id _objc_rootInit(id obj) { // In practice, it will be hard to rely on this function. // Many classes do not properly chain -init calls. return obj; }
先秦荀子的勸學中有言:
不積跬步,無以致千里;不積小流,無以成江海。
咱們在探索 iOS
底層原理的時候,應該也是抱着這樣的學習態度,注意點滴的積累,從小作起,聚沙成塔。下一篇筆者將對本文留下的兩個疑問進行解答: