本頁所使用的objc runtime 756.2,來自GITHUBc++
開始繼續學習研究OC源碼,此次研究的是isa的初始化和指向分析。git
看看蘋果文檔的介紹:github
isaobjective-c
A Pointer to the class definition of which this object is an instance.bash
isa : 一個指向該對象的類的指針。架構
打開Xcode,找到objc.h,咱們能夠看看到以下代碼ide
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
/// 一個展現OC類的未知的類型
typedef struct objc_class *Class;
/// Represents an instance of a class.
/// 展現一個類的實例
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
/// 一個指向類的實例的指針
typedef struct objc_object *id;
#endif
複製代碼
能夠看出,Class 是一個objc_class 類型的結構體。函數
而id類型,則是objc_object 類型的結構體.學習
在此以前,先回顧一下對象初始化的流程圖優化
在這裏,初始化實例的isa,其中 cls
爲初始化的類對象,hasCxxDtor
即爲是否含有C++的析構器。
咱們進入 initIsa(cls, true, hasCxxDtor)
這個函數,看看內部實現了什麼
if (!nonpointer) {
isa.cls = cls;
}
複製代碼
nonpointer概念:
表示是否對isa 指針開啓指針優化
0: 純isa指針
1: 不止是類對象的地址,還包含類信息、對象的引用計數等。
此時,若是爲純isa指針,將當前類 cls
賦值給 isa
的綁定屬性 cls
爲何有這個綁定屬性,而isa
到底是什麼看結構呢?
點擊isa.cls = cls;
中的cls
查看它的結構,以下:
union isa_t {
isa_t() { } // isa 初始化方法
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
複製代碼
能夠看見isa 是一個 union,聯合體,裏面包含了
isa_t
初始化方法isa_t(uintptr_t value)
工廠方法Class cls
綁定屬性ISA_BITFIELD
位域ISA_BITFIELD概念
咱們點開類型爲struct
的ISA_BITFIELD
,結構以下:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
複製代碼
NONPOINTER_ISA效果圖(手繪中,待補全......)
還原isa_t 的結構
咱們這時發現,isa的總體結構能夠替換爲以下的樣子:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
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;
// ISA_BITFIELD; // defined in isa.h
};
#endif
};
複製代碼
nonpointer: 表示是否對 isa 指針開啓指針優化 0:純isa指針,1:不止是類對象地址**,isa** 中包含了類信息、對象的引用計數等(佔1位)
has_assoc: 關聯對象標誌位,0沒有,1存在(佔1位)
has_cxx_dtor: 該對象是否有 C++ 或者 Objc 的析構器**,若是有析構函數,則須要作析構邏輯,** 若是沒有**,**則能夠更快的釋放對象(佔1位)
**shiftcls:**存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針。(佔33位)
magic:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間 weakly_referenced:志對象是否被指向或者曾經指向一個 ARC 的弱變量,沒有弱引用的對象能夠更快釋放。(佔6位)
deallocating:標誌對象是否正在釋放內存(佔1位)
has_sidetable_rc:當對象引用計數大於 10 時,則須要借用該變量存儲進位(佔1位)
extra_rc:當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc 爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc。(佔1位)
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
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;
#endif
複製代碼
生成新的isa
:
isa_t newisa(0);
c++ 析構器:
newisa.has_cxx_dtor = hasCxxDtor;` 表示當前對象是否有C++的析構函數(destructor),若是沒有,釋放時會快速的釋放內存。
位域賦值
newisa.shiftcls = (uintptr_t)cls >> 3;
對存儲指針的值進行右移動3位賦值。
返回isa
isa = newisa;
關於isa的指向以及子類父類的關係,蘋果官方給出了一張圖以下所示:
咱們執行一項代碼以下,並對該行打斷點:
Person *object = [Person alloc];
複製代碼
咱們知道:對象裏的 isa ——指向——> 類。
如今咱們想知道類的內存空間結構,在控制檯執行以下指令x/4gx Person.class
,結果以下:
(lldb) x/4gx Person.class
0x100001130: 0x001d800100001109 0x0000000100b39140
0x100001140: 0x0000000101a46ed0 0x0000000200000007
複製代碼
因爲isa
是類對象的第一個屬性,咱們知道0x001d800100001109
是改對象的isa,咱們看看他指向哪裏呢,使用p/x
指令試試:
(lldb) p/x 0x001d800100001109
(long) $16 = 0x001d800100001109
複製代碼
糟糕,查看不到結果?怎麼回事?類的isa 格式須要強轉,能夠退一步,打印類的地址試試:
po 0x100001130
Person
複製代碼
原來如此,在內存空間裏,名爲Person的類的第一個位置,指向Person類,豈不是循環指向了?
非也非也,這裏指向的類,咱們把它稱爲元類(meta-class)
類的isa ——指向——> 元類
咱們如今得到元類的具體地址,找到isa
的MASK
(掩碼),值爲0x00007ffffffffff8
,
輸入如下指令:
(lldb) p/x 0x001d800100001109 & 0x00007ffffffffff8
(long) $17 = 0x0000000100001108
(lldb) po 0x0000000100001108
Person
複製代碼
獲得元類地址爲:0x0000000100001108,16進制打印一下:
(lldb) x/4gx 0x0000000100001108
0x100001108: 0x001d800100b390f1 0x0000000100b390f0
0x100001118: 0x0000000100f5a480 0x0000000400000007
複製代碼
能夠看到元類結構裏,isa指針爲 0x001d800100b390f1,繼續獲取它的指向,咱們經過與掩碼來計算:
(lldb) p/x 0x001d800100b390f1 & 0x00007ffffffffff8
(long) $21 = 0x0000000100b390f0
複製代碼
好嘞,拿到內存指針地址爲0x0000000100b390f0, 打印一下:
po 0x0000000100b390f0
NSObject
複製代碼
至此,咱們能夠看到元類的isa
指向它的上一級元類,也就是跟元類(root meta-class),爲NSObject。
因此得出: 元類的isa ——指向——> 根元類
咱們打印下根元類結構:
x/4gx 0x0000000100b390f0
0x100b390f0: 0x001d800100b390f1 0x0000000100b39140
0x100b39100: 0x0000000101a47020 0x0000000500000007
複製代碼
拿到它的isa,與掩碼繼續進行與運算
p/x 0x001d800100b390f1 & 0x00007ffffffffff8
(long) $27 = 0x0000000100b390f0
複製代碼
獲得的結果0x0000000100b390f0,與根元類0x0000000100b390f0,徹底吻合。
至此,咱們得出結論:根元類的isa ——指向——> 根類NSObject。
什麼?你不信,這些都是猜想,證明一下?
好的,建立以下代碼
void TestNSObject(){
// NSObject實例對象
NSObject *object1 = [NSObject alloc];
// NSObject類
Class class = object_getClass(object1);
// NSObject元類
Class metaClass = object_getClass(class);
// NSObject根元類
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元類
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 實例對象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
複製代碼
打印結果以下:
0x10066ddc0 實例對象
0x7fff9294a118 類
0x7fff9294a0f0 元類
0x7fff9294a0f0 根元類
0x7fff9294a0f0 根根元類
複製代碼
可見,除了NSObject 類是獨有的建立,其餘元類、根元類、根根元類,都是同樣的,由於都是NSObject,因此結果獲得了證實。
咱們再回到這幅圖,最紅是這樣的:
isa指向:
- 對象中的isa——> 類
- 類中的isa ——> 元類
- 元類中的isa ----> 根元類
- 根元類中的isa ----> 根元類
類繼承關係:
- 子類 ———superClass——— 父類
- 父類 ———superClass——— 根元類
- 根元類 ———superClass——— NSObject
- NSObject ———superClass——— nil