前言:
OC對象的本質到底是什么?里面到底是什么結構呢?我們常說的isa是什么?我們在iOS底層原理 01 : alloc&init中探索的obj->initInstanceIsa(cls, hasCxxDtor)
是將isa與類信息關聯
的,那么isa具體是如何與類信息關聯的呢?下面我們帶著這些問題一起來學習。
OC對象的本質
接下來我們一起探索OC對象的本質:
- 首先我們在新建好的工程的main.c文件里面自定義一個LGHPerson類
@interface LGHPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGHPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGHPerson *person = [LGHPerson alloc];
NSLog(@"Hello, World!");
}
return 0;
}
然后打開終端,將mian.c文件通過clang命令編譯成我們需要的main.cpp, 即:在終端輸入:
clang -rewrite-objc main.c -o main.cpp
打開mian.cpp,快速查詢LGHPerson,我們會發現如下code,我們發現clang編譯器會把
LGHPerson
編譯成struct LGHPerson_IMPL{}
結構體,所以LGHPerson類
的本質其實就是LGHPerson_IMPL{}的結構體
struct LGHPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //isa
NSString *_name;
};
總結:
OC對象的本質是結構體
,每一個結構體都有struct NSObject_IMPL NSObject_IVARS
的成員變量(即isa
),OC類的屬性會被編譯成對應結構體的成員變量。
探索obj->initInstanceIsa()具體實現?
我們從objc4源碼(傳送:可編譯調試的objc4源碼),找到initInstanceIsa()這個函數
-
initInstanceIsa()
里面會執行initIsa()
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
- 在
initIsa()
函數里面,如果是!nonpointer
, 通過初始化函數isa_t((uintptr_t)cls)
并賦值給isa
,否則新建isa_t
,并對isa_t的位域里面的變量進行賦初始值
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;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
isa的本質
接下來我們來探索一下isa,我們在initIsa()
函數里面,我們看到isa
是一個isa_t的聯合體
。
1. 最終我們在isa.h文件中,看到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;
};
#endif
};
2. ISA_BITFIELD
宏定義,我們看到定義的是位域, 在__arm64__
和__x86_64__
下里面定義的變量是相同的,只是所占的字節數略微有些不同。
# 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
endif
3. 所以其實isa_t
的真正結構是聯合體位域
,__x86_64__
下,我們可以看到位域里面所有變量所占的字節總數是64位,恰好是8字節
union isa_t {
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
};
};
4. 那么每個的作用是什么呢?這里總結一下:
nonpointer
:表示是否對 isa 指針開啟指針優化, 0:純isa指針,1:不?是類對象地址,isa 中包含了類信息、對象的引?計數等 (自定義的類nonpointer為1)has_assoc
:關聯對象標志位,0沒有,1存在has_cxx_dtor
:該對象是否有 C++ 或者 Objc 的析構器,如果有析構函數,則需要做析構邏輯, 如果沒有,則可以更快的釋放對象shiftcls
: 存儲類指針的值。開啟指針優化的情況下,在 arm64 架構中有 33 位?來存儲類指針,x86架構用44位來存儲magic
:?于調試器判斷當前對象是真的對象還是沒有初始化的空間weakly_referenced:志對象是否被指向或者曾經指向?個 ARC 的弱變量,沒有弱引?的對象可以更快釋放。
deallocating
:標志對象是否正在釋放內存has_sidetable_rc
:當對象引?技術?于 10 時,則需要借?該變量存儲進位extra_rc
:當表示該對象的引?計數值,實際上是引?計數值減 1,例如,如果對象的引?計數為 10,那么 extra_rc 為 9。如果引?計數?于 10,則需要使?到下?的 has_sidetable_rc。
總結:
1.isa_t
是一個聯合體,里面的成員變量共用一段內存
,成員變量之間setter方法互斥
(不能同時set),所以對cls賦值的時候就不能對bits賦值,對bits賦值的時候就不能對cls賦值。
2.shiftcls
是用來存儲類信息的。
使用lldb去體驗isa_t的結構
接下來我們去驗證到底LGHPerson類信息是否存儲到shiftcls里面?
-
首先斷點到initIsa()函數里面
斷點.png - 我們使用lldb打印一些信息
newisa.bits =ISA_MAGIC_VALUE
; 是給nonpointer
和magic
附上初始值。
(lldb) p newisa
(isa_t) $12 = {
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
}
}
從下面打印的信息來看,newisa.cls
和newisa.bits
是共用同一段內存
,0x001d800000000001的10進制就是8303511812964353。
(lldb) p newisa.cls
(Class) $9 = 0x001d800000000001
(lldb) p newisa.bits
(uintptr_t) $10 = 8303511812964353
(lldb) p/d 0x001d800000000001
(long) $11 = 8303511812964353
我們接著斷點往下走
打印
newisa
,我們看到shiftcls
已經有值了,因為newisa.shiftcls = (uintptr_t)cls >> 3;
將類存到shiftcls
p newisa
(isa_t) $13 = {
cls = LGPerson
bits = 8303516107940081
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 536871966
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
我們看到(uintptr_t)cls >> 3
是536871966,newisa.shiftcls
的值536871966,說明類信息確實是存放在shiftcls。
(lldb) p (uintptr_t)cls >> 3
(uintptr_t) $17 = 536871966
(lldb) p newisa.shiftcls
(uintptr_t) $18 = 536871966
驗證shiftcls確實是存放的是類信息(在__x86__
架構為例)
-
先在main函數打個斷點
圖片.png - 使用lldb打印一些信息
通過lldb查看objc的內存,得到isa
是0x001d8001000020e9
(lldb) po objc
<LGHPerson: 0x100681f90>
(lldb) x/4gx 0x100681f90
0x100681f90: 0x001d8001000020e9 0x0000000000000000
0x100681fa0: 0x756e654d534e5b2d 0x776569566d657449
我們通過移位操作,獲取isa
在[3-46]位置上的值
,通過與LGHPerson.class
比對,發現是一致的,都是0x00000001000020e8
。
(lldb) p/x 0x001d8001000020e9>>3
(long) $4 = 0x0003b0002000041d
(lldb) p/x 0x0003b0002000041d<<20
(long) $5 = 0x0002000041d00000
(lldb) p/x 0x0002000041d00000>>17
(long) $6 = 0x00000001000020e8
(lldb) p/x LGHPerson.class
(Class) $7 = 0x00000001000020e8 LGHPerson
我們也可以通過算法 & ISA_MASK (0x00007ffffffffff8ULL)
,來得到shiftcls
的值,
我們看的也能得到0x00000001000020e8
(lldb) p/x 0x001d8001000020e9 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x00000001000020e8
補充
lldb相關的調試命令
p/x //以16進制輸出
p/d //以10進制輸出
p/o // 以8進制輸出
p/t // 以2進制輸出
p/c // 打印字符
x // 讀取內存 (等價于 memory read)
memory read // 讀取內存
x/4gx // 讀取4段內存(每段8字節,即32字節)
x/6gx // 讀取6段內存(每段8字節,即48字節)
x/8gx // 讀取8段內存(每段8字節,即64字節)
po <expr> // 打印對象的 description 方法的結果
help // 列出所有的命令
help <command> // 列出某個命令更多的細節,例如 help print
移位運算
比如有一段8位的內存,我們如何從取出【2-6】位上的值呢?請看下面示意圖:
先
>>2
,然后<<3
,最后>>1
,最后得到0111 1000
。
LLVM與Clang
-
LLVM
是構架編譯器(compiler)的框架系統
,以C++編寫而成,用于優化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time),對開發者保持開放,并兼容已有腳本。 -
Clang
是一個C++編寫、基于LLVM、發布于LLVM BSD許可證下的C/C++/Objective-C/Objective-C++編譯器
。比起GCC,Clang
編譯速度快
、占用內存小
、非常方便進行二次開發
。
下圖是LLVM和Clang的關系圖
總結 :
1. Clang相當于編譯過程的前端,而LLVM相當于編譯過程的后端。
2. Clang側重于語法語義分析
和生成中間代碼
,而LLVM側重于代碼優化
,生成目標程序
。
3.clang命令編譯mian.c的指令:clang -rewrite-objc main.m -o main.cpp