在 alloc的初探 中瞭解瞭如何獲取對象大小,內存對齊的原則,對象的 alloc
,可是在調用 calloc
在堆上開闢一個內存空間時返回了一個指針地址,這時候咱們如何將這個指針地址和當前對象關聯呢?bash
下方源碼中 initInstanceIsa
就乾的是這些事情。數據結構
//...
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
//...
複製代碼
簡單點來講,其實就是英文 is a
寫到一塊兒了,說明某一個對象是什麼。好比: object is a NSObject
,可是 isa
通過發展以後存儲的東西變得至關的龐大。架構
官方對 isa
的解釋爲: 每一個對象都是經過 isa
實例變量鏈接到運行時系統,從 NSObject
類繼承。Isa
標識對象的類;它指向一個結構的類定義編譯。ide
經過 ISA
,能夠在運行時找到一個對象的全部信息,如繼承層次結構中的位置,它的實例變量的大小和結構,以及能夠相應消息的方法所實現的位置。函數
進入 initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
函數以後發現,isa
是一個 isa_t
的類型。post
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
複製代碼
# if __arm64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# elif __x86_64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
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
類型是一個 union
聯合體。ISA_BITFIELD
是位域。ui
聯合體又被稱爲共用體,顧名思義就是在 union
定義下的變量共用一塊內存單元,賦值時相互覆蓋,這種結構被稱爲聯合體。spa
有些信息在存儲時,並不須要佔用一個完整的字節, 而只需佔幾個或一個二進制位。例如在存放一個開關量時,只有 0 和 1 兩種狀態, 用一位二進位便可。debug
爲了節省存儲空間,並使處理簡便,C語言又提供了一種數據結構,稱爲 位域 或 位段。指針
所謂 位域 是把一個字節中的二進位劃分爲幾 個不一樣的區域,並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做。 這樣就能夠把幾個不一樣的對象用一個字節的二進制位域來表示。
位段成員必須聲明爲 int
、unsigned int
或 signed int
類型(short char long
)。
位域列表定義的形式爲: 類型說明符 位域名:位域長度
struct 位域結構名
{
位域列表
};
複製代碼
例如:
struct bits
{
int a:8;
int b:2;
int c:6;
};
複製代碼
說明 bits
,共佔兩個字節。其中位域 a
佔 8
位,位域 b
佔 2
位,位域 c
佔 6
位。
isa_t
中 bit
是 unsigned long
類型,佔用 8
個字節,也就是 8byte = 64 bit
,因此在 __arm64__
和 __x86_64__
下 isa_t 的位域大小都是 64
個 bit
,只是由於架構不一樣因此佔得位數不一樣罷了。
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
//...
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
//...
isa = newisa;
}
}
複製代碼
在初始化 isa
的時候會將 alloc
出來的對象和類進行綁定 obj -> isa -> shiftcls
。
當執行 TestClass *object = [TestClass alloc];
建立一個對象的時候,object
的 isa
已經建立完畢了,而且將對象的類寫入了 shiftcls
。
用 x/4gx
以16進制打印4個8字節的內存值,那麼第一個必定是 isa
。
p/t
以二進制打印 isa
的內存值 0x001d800100001139
,獲取到64個 bit
的二進制。p/x TestClass.class
打印 TestClass
的類, 獲取指針地址。(uintptr_t)cls >> 3
還原。最後比對結果是同樣的。 $2 = 0b0000000000000000000000000000000100000000000000000001000100111000
$7 = 0b0000000000000000000000000000000100000000000000000001000100111000
在 objc_class()
函數裏就有對類的返回,使用的蒙版取 3~48 位。
inline Class
objc_object::ISA()
{
//...
//這裏是返回類對象須要用 isa 的指針 & ISA_MASK
return (Class)(isa.bits & ISA_MASK);
}
複製代碼
驗證以下:
既然已經知道了對象的 isa
指向了類,那類究竟是怎樣的呢?對象在實例化的時候,每一個對象都是不一樣的,其指針地址也是不相同的,那麼類呢?可否在內存中存在多份?
下方驗證一下:
Class class1 = [TestClass class];
Class class2 = [TestClass alloc].class;
Class class3 = object_getClass([TestClass alloc]);
Class class4 = [TestClass alloc].class;
NSLog(@"\n%p \n%p \n%p \n%p \n",class1,class2,class3,class4);
打印結果過下:
2019-12-22 15:33:01.539549+0800 objc-debug[3353:135796]
0x100001170
0x100001170
0x100001170
0x100001170
複製代碼
發現類對象的地址都是相同的,說明類對象在內存中有且只能存在一個。
在對象返回前,會給 isa
進行賦值,標識對象屬於什麼類,對象指向的類其實也是一個對象,這種對象被稱爲類對象。
既然是對象那一定存在 isa
,那麼類對象的 isa
又指向什麼呢?
x/4gx
打印類的內存結構:
打印出來的內存結構,其實都是內存的值,真正指向當前對象的指針地址是最前面的 0x100001138
,po 0x100001138
發現這個對象居然也是 TestClass
類型的,這個 TestClass
實際上是指向類對象的類,又稱爲元類。
元類是系統建立的,當程序中有一個類被定義,在編譯器編譯時,會相應的生成一個指向類對象的元類,以便以保存類的一些相關信息,好比:類方法等。
咱們知道了對象的 isa
指向類對象,類對象的 isa
指向元類,那麼元類的 isa
指向什麼呢?
接下來使用上方證實對象的 isa
指向的方法來推導一下元類的 isa
的指向。
推導過程以下:
從上圖能看到 TestClass
的類對象的 isa
指向 TestClass
的元類。
繼續查看 TestClass
元類的 isa
指向的是 NSObject
,那這個 NSObject
究竟是元類仍是類對象呢?
由於內存中類對象只有一個,類對象指針地址惟一,因此若是 NSObject.class
的指針地址和 TestClass
元類的 isa
指向地址相同則說明是 isa
指向 NSObject
類,不然不是。
繼續查看 NSObject
類的內存地址, p/x NSObject.class = 0x0000000100b38140 NSObject
,很遺憾並非 NSObject
的類對象。
查看 NSObject
類對象的元類,發現了 NSObject
元類的指針地址和 TestClass
元類 isa
指向的地址是相同的,這就說明了 TestClass
元類的 isa
指向的是 NSObject
元類。
繼續查看 NSObject
元類 isa
的指向,發現指向的是本身。
因此就有了蘋果老大給的下方的圖。
在上方的指向中,subclass
的 meta class
直接指向了 NSObject
的 meta class
,是由於元類保存的就是類的信息,能夠說已經到頭了, 若是再次繼承 Super meta class
已經沒有什麼意義,而且還會讓繼承關係更加複雜,使得繼承樹更加難以維護,因此蘋果將 subclass
的 meta class
直接指向了 NSObject
的 meta class
,再將 NSObject
的 meta class
指向了本身造成了一個閉環。
以上就是 isa
的初始化過程和指向分析。