1. 程式人生 > 其它 >OC-從alloc開始看底層

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 可以發現

通過allo或者new創建出來的物件最小的大小是16個位元組,_class_createInstanceFromZone只是計算物件開闢需要的記憶體大小,系統實際為物件分配的記憶體大小依賴的是calloc函式,

 

 而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.結構體的總大小,其大小就是其中最大成員的整數倍