首先看個 🌰objective-c
從圖中咱們能夠看出,三個對象的地址都不相同,由於這是聲明的三個不一樣的對象,可是值都是指向的同一個地址,而是因爲他們都是經過 obj1
這個對象賦值,指向的都是同一個對象,而這個對象是經過 alloc
方法建立的,並且 obj2
調用了 init
方法以後仍是相同的值,說明這個對象就是被alloc
方法建立而後返回的。算法
NSObject
對於 iOSer 來講或許已經熟悉得不能再熟悉,開發中大部分的對象都是繼承子它,建立的對象都是基於 NSObject
類,同時也提供了不少方法;萬物皆有出處,深刻思考後咱們可能會疑惑到底這個對象是怎麼生成的,底層作了什麼處理,有哪些不同的設計思想和實現方式;所以讓咱們一塊兒開啓三顧 alloc 旅程
。編程
要探險首先也得作好攻略,準備生存裝備和技能;那麼探索源碼,也須要正確的知(姿)識(勢),纔不會致使摸不到方向,無功而返,這裏介紹三種姿式供你們入門 ^_^ 。api
按住control + in
進入調試查看彙編調用信息;這裏沒有覺得是模擬器環境,因此沒有跟蹤到objc_alloc
的相關信息;使用真機狀況下能看到定位到 libobjc.A.dylib objc_alloc
,因此是要探究的方法在 libobjc.A.dylib
這個庫中。sass
要探究alloc
方法,因此咱們添加一個alloc
的符號斷點,這時候會顯示斷到不少類的alloc
方法,可是沒有關係,由於咱們斷點了代碼所在對象,因此咱們直接調試就行;斷點執行下一步,就能看到 libobjc.A.dylib + [NSObject alloc]
,說明是調用了libobjc.A.dylib
庫中的NSObject
的 alloc
類方法。安全
[MRObject alloc]
的彙編代碼下一步是調用
objc_alloc
; 因此咱們須要研究
objc_alloc
方法即爲
alloc
的底層實現方法!
蘋果開源源碼地址 => opensource.apple.com/tarballs/多線程
搜索 objc
便可看到 objc 源碼地址 當前 objc4-756.2 爲最新版本!架構
ps: 在objc
源碼中咱們看到不少 old 和 new 的文件命名標識,這表明objc
在迭代過程當中過渡了兩個版本,一個新版,一箇舊版,如今咱們都是使用的新版本api
,因此查看源碼邏輯的時候只須要定位 objc2
或者 new
標識的新版本~app
經過查看objc
源碼和彙編的指令查看 objc_alloc
的流程發現很難定位跟蹤,不能直觀的跟蹤到具體的調用方法路徑;因此將objc
源碼集成到咱們的調試工程中,經過打斷點調試跟蹤才能一目瞭然,暢通無阻,所向披靡...以上一切都是理想YY,hahaha... ^_^;如何集成objc
源碼,移步 => iOS_objc4-756.2 最新源碼編譯調試post
alloc
=> objc_rootAlloc
callAlloc
首先看 __OBJC2__
的分支,如今是使用的新版本;而後看第一個判斷fastpath(!cls->ISA()->hasCustomAWZ())
,這裏是判斷該類是否有重寫initWithZone;第二層判斷 fastpath(cls->canAllocFast())
,是否能夠快速建立,這裏會返回false
,具體實現見下分析。
canAllocFast
調用 bits.canAllocFast方法
bool canAllocFast() {
assert(!isFuture());
return bits.canAllocFast();
}
複製代碼
跟蹤 canAllocFast
實現
查看 FAST_ALLOC
宏定義
class_createInstance
打印obj
的值,印證流程
現代計算機中,內存空間按照字節劃分,理論上能夠從任何起始地址訪問任意類型的變量。但實際中在訪問特定類型變量時常常在特定的內存地址訪問,這就須要各類類型數據按照必定的規則在空間上排列,而不是順序一個接一個地存放,這就是字節對齊。
不一樣硬件平臺對存儲空間的處理上存在很大的不一樣。某些平臺對特定類型的數據只能從特定地址開始存取,而不容許其在內存中任意存放。例如Motorola 68000 處理器不容許16位的字存放在奇地址,不然會觸發異常,所以在這種架構下編程必須保證字節對齊。
但最多見的狀況是,若是不按照平臺要求對數據存放進行對齊,會帶來存取效率上的損失。好比32位的Intel處理器經過總線訪問(包括讀和寫)內存數據。每一個總線週期從偶地址開始訪問32位內存數據,內存數據以字節爲單位存放。若是一個32位的數據沒有存放在4字節整除的內存地址處,那麼處理器就須要2個總線週期對其進行訪問,顯然訪問效率降低不少。
所以,經過合理的內存對齊能夠提升訪問效率。爲使CPU可以對數據進行快速訪問,數據的起始地址應具備「對齊」特性。好比4字節數據的起始地址應位於4字節邊界上,即起始地址可以被4整除。
此外,合理利用字節對齊還能夠有效地節省存儲空間。但要注意,在32位機中使用1字節或2字節對齊,反而會下降變量訪問速度。所以須要考慮處理器類型。還應考慮編譯器的類型。在VC/C++和GNU GCC中都是默認是4字節對齊。
1.數據類型自身的對齊值:char型數據自身對齊值爲1字節,short型數據爲2字節,int/float型爲4字節,double型爲8字節。
2.結構體或類的自身對齊值:其成員中自身對齊值最大的那個值。
3.指定對齊值:#pragma pack (value)時的指定對齊值value。
4.數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中較小者,即有效對齊值=min{自身對齊值,當前指定的pack值}。
instanceSize
獲取實例對象內存大小size_t instanceSize(size_t extraBytes) {
// 對齊的大小 + extraBytes(額外加的字節數)
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
// 這裏選擇最小16,由於已經至少8字節了,爲了防止在讀取或者存儲的時候多線程越界之類的安全性;16處於一個基本的合理節約的大小,cup讀取的時候比較合理
if (size < 16) size = 16;
return size;
}
複製代碼
// alignedInstanceSize
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
// 調用 unalignedInstanceSize 獲取內存字節大小,而後調用 word_align 方法對齊
return word_align(unalignedInstanceSize());
}
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL // 7
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
// 對齊算法 (8字節對齊)
// ps: &與運算,同爲1則爲1,否爲0;|或運算,一個爲1則爲1,否爲0;^異或運算,兩個不相同則爲1,否爲0;~非運算,取反
static inline uint32_t word_align(uint32_t x) {
// WORD_MASK = 7,先非運算,而後與運算,二進制
// 例如這裏是 x = 8; 由於 MRObject 類結構咱們沒有聲明其餘屬性之類的,只有默認包含的 isa 屬性結構,佔用8字節
// x+mask = 8 + 7 = 15 => 0000 1111
// mask => 0000 0111
// ~7 非運算,取反
// a => 1111 1000
// & 與運算,同時爲1則爲1
// x+mask => 0000 1111
// return 8 => 0000 1000
// 本質就是 mask = 7 操做 => 獲得8的倍數,8字節對齊,以8爲倍數;至關於 x >> 3 << 3, 右移三位,左移三位 (枚舉定義也是如此,計算快速)
return (x + WORD_MASK) & ~WORD_MASK;
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
// 對象的實例內存字節大小,是根據類的ivarList、methodList、propertyList、protocolList 等相關的屬性相加計算出來的
// 這些數據存儲在類結構中的 data 段的 ro 結構中,這裏經過 ro 獲取具體須要建立的字節數大小
return data()->ro->instanceSize;
}
複製代碼
可能會疑惑爲何須要8字節對齊,(⊙o⊙)…???
cpu
讀取的時候不知道連續的內存開闢的大小,訪問的時候可能一個1字節,4字節,8字節...,而後讀取的時候須要先判斷多長而後在讀取,比較麻煩;所以cpu就想着若是每一段都是8字節那麼讀取的時候就比較方便,空間換取時間。
怎麼印證本身對齊呢,那麼咱們經過LLDB
調試打印對象內存結構,查看信息來驗證。
首先類聲明幾個屬性:
@property (nonatomic, copy) NSString *name; // 8個字節
@property (nonatomic, assign) int age; // 4個字節
@property (nonatomic, assign) long height; // 8個字節
@property (nonatomic, copy) NSString *hobby; // 8個字節
複製代碼
😤說好的能所有打出呢,嗯哼?爲啥沒能訪問到hobby
呢?這是由於咱們只打印了四段內存地址,而加上isa
一共是默認5個屬性,因此hobby
在第五段內存,下面是打印日誌信息😺。
(lldb) x/5xg obj1
0x1010a1f40: 0x001d800100001521 0x0000000000000012
0x1010a1f50: 0x00000001000010a8 0x00000000000000b9
0x1010a1f60: 0x00000001000010c8
(lldb) po 0x00000001000010c8
money
複製代碼
alloc
流程圖以上爲簡要分析alloc
流程,有歧義歡迎指出,持續更新進階之旅,未完待續。。。