該文章是對前一篇 OC底層原理01-alloc流程探索 中initInstanceIsa
(給對象關聯isa)分支的一個拓展和深入
一、準備工作
-
objc4
可編譯源碼,可直接跳到文章最后,下載調試好的源碼
二、源碼里面找isa
2.1 繼續obj->initInstanceIsa(cls, hasCxxDtor);
流程
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
- 我們自定義的類都是走
initInstanceIsa
- 系統某一些類才會調用
initIsa
2.2 進入initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
-
initInstanceIsa
也會進入initIsa
- 它們第二個參數
nonpointer
不同 -
initInstanceIsa
是傳的true,initIsa
是傳的false
2.3 進入initIsa
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
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
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
- 由于我們自定義的類
nonpointer
都是true,所以我們會進入else
流程 - 發現目標
isa
,它是一個isa_t
類型的對象 -
isa_t newisa(0)
初始化一個isa
-
newisa.bits = hasCxxDtor
給屬性bits
賦值 -
newisa.has_cxx_dtor = hasCxxDtor
給屬性has_cxx_dtor
賦值 -
newisa.shiftcls = (uintptr_t)cls >> 3
給屬性shiftcls
賦值,并向右移三位 -
isa = newisa
把新建的newisa
賦值給對象的isa
,進行關聯
2.4 查看isa_t
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
};
-
isa_t
是一個聯合體,屬性互斥。 -
cls
和bits
不能同時賦值
2.5 查看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)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
- 如果是
arm64
就走上面,如果是x86_64
就走下面 - 因為我們配置的
objc4
是基于macos
的,所以我們要研究的是下面 -
isa
存在內存地址的第一段,也就是8字節,8*8=64位
2.6 斷點分析newisa
的賦值過程
2.6.1 newisa.bits = ISA_MAGIC_VALUE
在這一行下斷點
image.png
2.6.2 打印newisa
(lldb) p newisa
(isa_t) $2 = {
cls = nil
bits = 0
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
- 初始都為空
2.6.3 斷點往下走一步,再次打印newisa
(lldb) p newisa
(isa_t) $3 = {
cls = 0x001d800000000001
bits = 8303511812964353
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
-
cls
被賦值了0x001d800000000001
-
bits
被賦值了8303511812964353
-
nonpointer
被賦值了1
-
magic
被賦值了59
2.6.3.1 系統給cls
賦值的是什么
image.png
- 宏
ISA_BITFIELD
上面一個宏# define ISA_MAGIC_VALUE 0x001d800000000001ULL
,這一步可以看做把ISA_MAGIC_VALUE
直接賦值給cls
,與打印臺打印的cls
不是同一個,這里的cls
是newisa
中的cls
- 這里的
ULL
是unsigned long long
無符號長整型,只是一個類型標識
2.6.3.2 系統給bits
賦值的是什么
image.png
- 原來系統把宏
ISA_MAGIC_VALUE
的值0x001d800000000001
轉成了10進制,賦給了newisa
中的cls
- 注意:上圖中打印的
cls
,是objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
方法傳入的,為了驗證當前研究的是GomuPerson
的流程
2.6.3.3 系統給nonpointer
賦值的是什么
- 因為
initIsa
方法傳入的是一個true,所以賦值為1
2.6.3.4 系統給magic賦值的是什么
image.png
- 用
p/t
命令將0x001d800000000001
轉成2進制打印,得上以上截圖的一串數字 - 由宏
ISA_BITFIELD
的位域定義可以得到,magic
占位是6位
,從第47位到第52位
,由于是iOS小端
,從右到左讀取,用分隔符每4位分開方便查看0b0000_0000_0001_1101_1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001
,得到第47位到52位為:111011
-
111011
是2進制
,轉成10進制
得到59
2.6.4 斷點繼續往下走一步,再次打印newisa
(lldb) p newisa
(isa_t) $7 = {
cls = 0x001d800000000005
bits = 8303511812964357
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 1
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
-
has_cxx_dtor
被賦值了1
,前面傳過來hasCxxDtor
為true
2.6.4 斷點繼續往下走一步,再次打印newisa
(lldb) p newisa
(isa_t) $8 = {
cls = GomuPerson
bits = 8303516107940741
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 1
shiftcls = 536872048
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
-
cls
的值變成了GomuPerson
,說明isa
和GomuPerson
已關聯 -
bits
的值由8303511812964357
變成了8303516107940741
-
shiftcls
被賦值了536872048
2.6.4.1 shiftcls
賦的值是什么
image.png
- 因為
newisa.shiftcls = (uintptr_t)cls >> 3
這一步代碼,將cls
強轉成uintptr_t
(無符號長整型的別名),然后右移位 - 打印
(uintptr_t)cls
得到4294976384
- 將
4294976384
右移三位,得到536872048
- 這一步的目的就是將
cls
強轉后右移三位的值賦給shiftcls
- 因為
newisa
中又給shiftcls
賦了值,所以bits
必然會變
2.6.4.2 bits
的值為什么變了
[圖片上傳中...(image.png-902d6d-1599838772142-0)]
- 把
bits
當前的值換算成2進制打印,得到上圖$27
- 把
shiftcls
的值換算成2進制打印,得到上圖$28
- 把
$28
前面補零,補足44位,則與$27
完全相同 - 說明
isa
中3-46
位的確存的是cls
,但是是右移了3位
的cls
2.6.5 反向由person
的首地址isa
推導出shiftcls
2.6.5.1 打印person
的isa
的地址,轉成10進制
image.png
- 得到
8303516107940741
,很上面newisa
中的bits
完全一樣
2.6.5.2 由isa
的地址推導給shiftcls
賦值之前的(uintptr_t)cls
image.png
- 由于
shiftcls
在isa
內存的3-46
位,所以我們要先抹掉前面3位,右移>>
3位 - 左邊空出20位,左移
<<
20位,再右移>>
17位,把左邊17位抹零
- 得到結果
4294976384
,和給shiftcls
賦值的(uintptr_t)cls
的值一模一樣
2.6.5.3 shiftcls
賦值的時候將(uintptr_t)cls
右移三位的目的
- 需要將一個64位的
(uintptr_t)cls
賦值給一個從isa
第三位開始長度只有44的shiftcls
- 那么我們需要取
(uintptr_t)cls
的0-43
位賦值給shiftcls
,因為shiftcls
是從isa
的第三位開始,這樣賦值的后果是shiftcls
在isa
中的值就會向左偏移移3位, - 為了保證賦值后的
shiftcls
和(uintptr_t)cls
相等,我們必須把(uintptr_t)cls
的3-46
賦值給shiftcls
,即(uintptr_t)cls>>3
2.6.6 isa
的地址 &
ISA_MASK
,會得到 GomuPerson
image.png
- 經驗證,
isa
的地址&
ISA_MASK
,確定會得到GomuPerson
- 把
ISA_MASK
轉成二進制
,發現只有中間3-46
位有效,所以isa
與上ISA_MASK
,相當于把前3位
和后17位
置0
2.7 基于x86_64
的位域圖
image.png
-
nonpointer
:表示是否對isa
指針開啟指針優化,占1位-
0
:純isa
指針 -
1
:不只是類對象地址
,isa
中包含了類信息
、對象的引用計數
等
-
-
has_assoc
:表示關聯對象標志位,占1位-
0
:沒有關聯對象 -
1
:存在關聯對象
-
-
has_cxx_dtor
:表示該對象是否有C++/OC的析構器
(dealloc
),占1位-
0
:沒有析構函數,則可以更快的釋放對象 -
1
:有析構函數,則需要做析構邏輯
-
-
shiftclx
表示存儲類的指針的值
(類的地址), 即類信息-
arm64
中占33
位,開啟指針優化
的情況下,在arm64
架構中有33
位用來存儲類指針 -
x86_64
中占44
位
-
-
magic
:用于調試器判斷當前對象是真的對象
還是沒有初始化的空間
,占6位 -
weakly_refrenced
:表示對象是否被指向
或者曾經指向一個ARC
的弱變量
,沒有弱引用的對象
可以更快釋放
,占1位 -
deallocating
:標志對象是是否正在釋放內存
,占1位 -
has_sidetable_rc
:表示當對象引用計數大于10
時,則需要借用該變量
存儲進位
,占1位 -
extra_rc
:(額外的引用計數) --- 導尿管表示該對象的引用計數值
,實際上是引用計數值減1
,如果對象的引用計數為10
,那么extra_rc
為9
,占8位
四、拓展知識
構造數據類型的方式:
-
結構體
[struct
]
結構體
是指把不同的數據
組合成一個整體
,其變量是共存的
,變量不管是否使用,都會分配內存
。
優點:存儲容量較大
,包容性強
,且成員之間不會相互影響
缺點:所有屬性都分配內存
,比較浪費內存
,假設有4個int成員,一共分配了16字節的內存,但是在使用時,你只使用了4字節,剩余的12字節就是屬于內存的浪費
-
聯合體
[union
]
聯合體
也是由不同的數據
類型組成,但其變量是互斥的
,所有的成員共占一段內存
。而且聯合體
采用了內存覆蓋技術
,同一時刻
只能保存一個成員的值
,如果對新的成員賦值
,就會將原來成員的值覆蓋掉
優點:所有成員共用一段內存
,使內存的使用更為精細靈活
,同時也節省了內存空間
缺點:包容性弱