OC-從alloc開始看底層
一、底層物件的alloc和init
我們一般在建立物件時,都會通過[X alloc] init]這樣的方式建立物件,但是allco的底層是如何呼叫的,init的作用是什麼呢,我們來探索一下:
當我們在通過 alloc 和init分開呼叫時可以發現,兩次init的呼叫對於p1,p2來說,他們都是屬於alloc創建出來的同一個物件,這是為什麼呢?我們來通過彙編查詢一下(彙編需要在真機上執行),我們在alloc的地方打個斷點,同時勾選 code -> Debug -> Debug Workflow -> Always Show Disassembly 後執行。我們可以發現通過讀取x1,x0 暫存器發現alloc已經建立好了物件,而沒有見到init的身影。
小小補充一下:彙編中:b bl是跳轉指令、ret 是函式的返回、另外暫存器一般儲存返回的結果,還有運算器控制器等。
二、objc4-838原始碼分析alloc呼叫
我們通過對alloc不斷跳轉可以發現,callAlloc(帶*的地方)執行了兩次,第一次在執行了objc_msgSend方法,第二次執行了_objc_rootAllocWithZone方法,這是屬於callAlloc的兩種不同方法的執行分支(if),是否執行的判斷條件在於fastpath(!cls->ISA()->hasCustomAWZ()) ,這個是判斷cls是否被初始化以及是否自定義了allocWithZone方法,最後我們發現alloc在建立物件時是通過_class_createInstanceFromZone創建出來的,此時我們可以總結出alloc的底層實現流程:(alloc最終返回 一個obj)
_class_createInstanceFromZone 這個方法可能會被編譯器優化掉。
三 init的作用(new)
我們從objc的原始碼中可以看出init方法並沒有做任何實質性的操作,就是一個工廠模式,可以讓我們重寫這個方法來初始化一些自定義的操作。
四、alloc的記憶體申請
在這裡我們需要先知道一點,在64位的作業系統中,記憶體是按照8位對齊的,在oc中,物件是一個objc_object的結構體,儲存的是isa+成員變數的值。
接下來我們看一下_class_createInstanceFromZone的內部是如何操作的
看程式碼if (size < 16) size = 16 可以發現
而calloc函式是是以16位元組對齊的,那麼這裡就有問題了。為什麼為物件開闢的空間是8位元組,而儲存的時候是16位元組呢?,我們接著看_class_createInstanceFromZone方法
我們可以看到這裡執行了一個initInstanceIsa方法,其實這裡是把calloc返回的記憶體地址關聯到了相應的類上,由此我們可以發現_class_createInstanceFromZone方法內部主要做了以下內容:
五 物件的對齊
我們剛剛已經知道了物件是一個objc_object的結構體,儲存的是isa+成員變數的值,那麼我們需要解釋以下兩點:
1.為什麼需要位元組對齊
其實位元組對齊的根本就是為了加快CPU訪問的速度,這就是一種以空間換時間的方式,,我們都知道位元組是記憶體讀取的單位,但CPU讀取的單位卻是塊,我們通過位元組對齊的方式來形成塊,加塊CPU讀取的速度。
2.為什麼物件的成員變數以8位元組對齊,而系統實際分配的記憶體是以16位元組對齊呢?
OC的物件中,有一個isa指標,佔了8位位元組,就算物件中沒有其他的屬性了,也⼀定有⼀個isa,那物件就⾄少要佔⽤8位位元組。如果以8位位元組對⻬的話,如果連續的兩塊記憶體都是沒有屬性的物件,那麼它們的記憶體空間就會完全的挨在⼀起,是容易混亂的。以16位元組為⼀塊,這就保證了CPU在讀取的時候,按照塊讀取就可以,效率更⾼,同時還不容易混亂。 六、結構體對齊(這部分在swift裡已經講過了 這裡我們以幾個例子來說明即可)struct LGStruct1 { //儲存準則,需要是當前這個變數佔據位元組長度的整數倍 double a;//double佔8個位元組,則儲存在0~7 char b;//char是1位元組 儲存在8 int c;//int是4位元組 儲存在12~15 short d;//short是2位元組,儲存在 16~17 }struct1;//總大小是內部最大成員的整數倍,所以是24 struct LGStruct2 { double a;//double佔8個位元組,則儲存在0~7 int b;//int是4位元組 儲存在8~11 char c;//char是1位元組 儲存在12 short d;//short是2位元組,儲存在 14~15 }struct2;//總大小是內部最大成員的整數倍,所以是16 struct LGStruct3 { double a;//double佔8個位元組,則儲存在0~7 int b;//int是4位元組 儲存在8~11 char c;//char是1位元組 儲存在12 short d;//short是2位元組,儲存在 14~15 int e;//int是4位元組 儲存在16~19 struct LGStruct1 str;//從內部最大元素大小的整數倍開始儲存,24開始儲存 24~41 }struct3;//總大小必須為內部最大成員的整數倍,所以是8的整數倍,所以struct的大小為48
對齊原則按照下面的方式對齊:
1 資料成員對齊規則:結構體的第一個資料成員放在offset為0的地方,以後每個資料成員儲存的起始位置從該成員的整數倍開始儲存
2.結構體作為其他結構體中的一個成員時,則從該這個結構體中內部最大元素的整數倍地址開始儲存
3.結構體的總大小,其大小就是其中最大成員的整數倍