iOS底層原理探索篇 主要是圍繞底層進行
源碼分析
-LLDB調試
-源碼斷點
-彙編調試
,讓本身之後回顧複習的😀😀html目錄以下:c++
咱們在iOS底層原理探索 — 內存對齊&malloc源碼分析一文中講解到NObject的底層實現其實就是一個包含一個isa
指針的結構體:架構
struct NSObject_IMPL {
Class isa;
};
複製代碼
在arm64
架構以前,isa
僅是一個指針,保存着類對象(Class)或元類對象(Meta-Class)的內存地址,在arm64
架構以後,蘋果對isa
進行了優化,變成了一個isa_t
類型的聯合體(union)結構,同時使用位域來存儲更多的信息:app
isa
指針並非直接指向類對象或者元類對象的內存地址,而是須要
& ISA_MASK
經過位運算才能獲取類對象或者元類對象的地址.
如今你們可能心存疑問,什麼是聯合體?什麼是位域?位運算又是什麼?不要着急,接下來一一爲你們解答.
位域是指信息在存儲時,並不須要佔用一個完整的字節, 而只需佔一個或幾個二進制位。例如生活中的電燈開關,它只有「開」、「關」兩種狀態,那咱們就能夠用1
和0
來分別表明這兩種狀態,這樣咱們就僅僅用了一個二進制位就保存了開關的狀態。這樣一來不只節省存儲空間,還使處理更加簡便。iphone
在計算機語言中,除了加、減、乘、除等這樣的算術運算符以外還有不少運算符,這裏只爲你們簡單講解一下位運算符。 位運算符用來對二進制位進行操做,固然,操做數只能爲整型和字符型數據。C
語言中六種位運算符:&
按位與、|
按位或、^
按位異或、~
非、<<
左移和>>
右移。 咱們依舊引用上面的電燈開關論,只不過如今咱們有兩個開關:開關A和開關B,1
表明開,0
表明關。ide
有0出0,全1出1.
A | B | & |
---|---|---|
0 | 0 | 0 |
1 | 0 | 0 |
0 | 1 | 0 |
1 | 1 | 1 |
咱們能夠理解爲在按位與運算中,兩個開關是串聯的,若是咱們想要燈亮,須要兩個開關都打開燈纔會亮,因此是1 & 1 = 1. 若是任意一個開關沒有打開,燈都不會亮,因此其餘運算都是0.
有1出1,全0出0.
A | B | I |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 1 |
在按位或運算中,咱們能夠理解爲兩個開關是並聯的,即一個開關開,燈就會亮.只有當兩個開關都是關的.燈纔不會亮.
相同爲0,不一樣爲1.
A | B | ^ |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
1 | 1 | 0 |
非運算即取反運算,在二進制中 1 變 0 ,0 變 1。例如110101
進行非運算後爲001010
,即1010
.
左移運算就是把<<
左邊的運算數的各二進位所有左移若干位,移動的位數即<<
右邊的數的數值,高位丟棄,低位補0。 左移n位就是乘以2的n次方。例如:a<<4
是指把a的各二進位向左移動4位。如a=00000011(十進制3),左移4位後爲00110000(十進制48)。
右移運算就是把>>
左邊的運算數的各二進位所有右移若干位,>>
右邊的數指定移動的位數。例如:設 a=15,a>>2 表示把00001111右移爲00000011(十進制3)
能夠利用按位與 &
運算取出指定位的值,具體操做是想取出哪一位的值就將那一位置爲1,其它位都爲0,而後同原數據進行按位與計算,便可取出特定的位.
例:
0000 0011
取出倒數第三位的值
// 想取出倒數第三位的值,就將倒數第三位的值置爲1,其它位爲0,跟原數據按位與運算
0000 0011
& 0000 0100
------------
0000 0000 // 得出按位與運算後的結果,便可拿到原數據中倒數第三位的值爲0
複製代碼
上面的例子中,咱們從0000 0011
中取值,則有0000 0011
被稱之爲源碼.進行按位與操做設定的0000 0100
稱之爲掩碼.
能夠經過按位或 |
運算符將某一位的值設爲1或0.具體操做是: 想將某一位的值置爲1的話,那麼就將掩碼中對應位的值設爲1,掩碼其它位爲0,將源碼與掩碼進行按位或操做便可.
例: 將
0000 0011
倒數第三位的值改成1
// 改變倒數第三位的值,就將掩碼倒數第三位的值置爲1,其它位爲0,跟源碼按位或運算
0000 0011
| 0000 0100
------------
0000 0111 // 便可將源碼中倒數第三位的值改成1
複製代碼
想將某一位的值置爲0的話,那麼就將掩碼中對應位的值設爲0,掩碼其它位爲1,將源碼與掩碼進行按位或操做便可.
例: 將
0000 0011
倒數第二位的值改成0
// 改變倒數第二位的值,就將掩碼倒數第二位的值置爲0,其它位爲1,跟源碼按位或運算
0000 0011
| 1111 1101
------------
0000 0001 // 便可將源碼中倒數第二位的值改成0
複製代碼
到這裏相信你們對位運算符有了必定的瞭解,下面咱們經過OC代碼的一個例子,來將位運算符運用到實際代碼開發中. 咱們聲明一個TCJCar
類,類中有四個BOOL
類型的屬性,分別爲front
、back
、left
、right
,經過這四個屬性來判斷這輛小車的行駛方向.
TCJCar
類對象所佔據的內存大小:
咱們看到,一個
TCJCar
類的對象佔據16個字節.其中包括一個
isa
指針和四個
BOOL
類型的屬性,8+1+1+1+1=12,根據
內存對齊原則,因此一個
TCJCar
類的對象佔16個字節.
咱們知道,BOOL
值只有兩種狀況:0
或1
,佔據一個字節的內存空間.而一個字節的內存空間中又有8個二進制位,而且二進制一樣只有0
或1
,那麼咱們徹底可使用1個二進制位來表示一個BOOL
值。也就是說咱們上面聲明的四個BOOL
值最終只使用4個二進制位就能夠,這樣就節省了內存空間。那咱們如何實現呢? 想要實現四個BOOL
值存放在一個字節中,咱們能夠經過char
類型的成員變量來實現.char
類型佔一個字節內存空間,也就是8個二進制位.可使用其中最後四個二進制位來存儲4個BOOL
值. 固然咱們不能把char
類型寫成屬性,由於一旦寫成屬性,系統會自動幫咱們添加成員變量,自動實現set
和get
方法.
@interface TCJCar(){
char _frontBackLeftRight;
}
複製代碼
若是咱們賦值_frontBackLeftRight
爲1
,即0b 0000 0001
,只使用8個二進制位中的最後4個分別用0
或者1
來表明front
、back
、left
、right
的值.那麼此時front
、back
、left
、right
的狀態爲:
front
、
back
、
left
、
right
的掩碼,來方便咱們進行下一步的位運算取值和賦值:
#define TCJDirectionFrontMask 0b00001000 //此二進制數對應十進制數爲 8
#define TCJDirectionBackMask 0b00000100 //此二進制數對應十進制數爲 4
#define TCJDirectionLeftMask 0b00000010 //此二進制數對應十進制數爲 2
#define TCJDirectionRightMask 0b00000001 //此二進制數對應十進制數爲 1
複製代碼
經過對位運算符的左移<<
和右移>>
的瞭解,咱們能夠將上面的代碼優化成:
#define TCJDirectionFrontMask (1 << 3)
#define TCJDirectionBackMask (1 << 2)
#define TCJDirectionLeftMask (1 << 1)
#define TCJDirectionRightMask (1 << 0)
複製代碼
自定義的set
方法以下:
- (void)setFront:(BOOL)front
{
if (front) {// 若是須要將值置爲1,將源碼和掩碼進行按位或運算
_frontBackLeftRight |= TCJDirectionFrontMask;
} else {// 若是須要將值置爲0 // 將源碼和按位取反後的掩碼進行按位與運算
_frontBackLeftRight &= ~TCJDirectionFrontMask;
}
}
- (void)setBack:(BOOL)back
{
if (back) {
_frontBackLeftRight |= TCJDirectionBackMask;
} else {
_frontBackLeftRight &= ~TCJDirectionBackMask;
}
}
- (void)setLeft:(BOOL)left
{
if (left) {
_frontBackLeftRight |= TCJDirectionLeftMask;
} else {
_frontBackLeftRight &= ~TCJDirectionLeftMask;
}
}
- (void)setRight:(BOOL)right
{
if (right) {
_frontBackLeftRight |= TCJDirectionRightMask;
} else {
_frontBackLeftRight &= ~TCJDirectionRightMask;
}
}
複製代碼
自定義的get
方法以下:
- (BOOL)isFront
{
return !!(_frontBackLeftRight & TCJDirectionFrontMask);
}
- (BOOL)isBack
{
return !!(_frontBackLeftRight & TCJDirectionBackMask);
}
- (BOOL)isLeft
{
return !!(_frontBackLeftRight & TCJDirectionLeftMask);
}
- (BOOL)isRight
{
return !!(_frontBackLeftRight & TCJDirectionRightMask);
}
複製代碼
此處須要注意的是,代碼中!爲邏輯運算符非,由於_frontBackLeftRight & TCJDirectionFrontMask
代碼執行後,返回的確定是一個整型數,如當front
爲YES
時,說明二進制數爲0b 0000 1000
,對應的十進制數爲8,那麼進行一次邏輯非運算後,!(8)
的值爲0
,對0
再進行一次邏輯非運算!(0)
,結果就成了1
,那麼正好跟front
爲YES
對應.因此此處進行兩次邏輯非運算,!!
. 固然,還要實現初始化方法:
- (instancetype)init
{
self = [super init];
if (self) {
_frontBackLeftRight = 0b00001000;
}
return self;
}
複製代碼
經過測試驗證,咱們完成了取值和賦值:
咱們在上文講到了位域
的機率,那麼咱們就可使用結構體位域
來優化一下咱們的代碼.這樣就不用再額外聲明上面代碼中的掩碼部分了.位域聲明格式是位域名: 位域長度
. 在使用位域
的過程當中須要注意如下幾點:
int
類型就不能超過32位二進位.使用位域優化後的代碼:
來測試看一下是否正確,此次咱們將front
設爲
YES
、
back
設爲
NO
、
left
設爲
NO
、
right
設爲
YES
:
依舊能完成賦值和取值. 可是代碼這樣優化後咱們去掉了掩碼和初始化的代碼,可讀性不好,咱們繼續使用聯合體進行優化:
咱們可使用比較高效的位運算來進行賦值和取值,使用union
聯合體來對數據進行存儲。這樣不只能夠增長讀取效率,還能夠加強代碼可讀性.
#import "TCJCar.h"
//#define TCJDirectionFrontMask 0b00001000 //此二進制數對應十進制數爲 8
//#define TCJDirectionBackMask 0b00000100 //此二進制數對應十進制數爲 4
//#define TCJDirectionLeftMask 0b00000010 //此二進制數對應十進制數爲 2
//#define TCJDirectionRightMask 0b00000001 //此二進制數對應十進制數爲 1
#define TCJDirectionFrontMask (1 << 3)
#define TCJDirectionBackMask (1 << 2)
#define TCJDirectionLeftMask (1 << 1)
#define TCJDirectionRightMask (1 << 0)
@interface TCJCar()
{
union{
char bits;
// 結構體僅僅是爲了加強代碼可讀性
struct {
char front : 1;
char back : 1;
char left : 1;
char right : 1;
};
}_frontBackLeftRight;
}
@end
@implementation TCJCar
- (instancetype)init
{
self = [super init];
if (self) {
_frontBackLeftRight.bits = 0b00001000;
}
return self;
}
- (void)setFront:(BOOL)front
{
if (front) {
_frontBackLeftRight.bits |= TCJDirectionFrontMask;
} else {
_frontBackLeftRight.bits &= ~TCJDirectionFrontMask;
}
}
- (BOOL)isFront
{
return !!(_frontBackLeftRight.bits & TCJDirectionFrontMask);
}
- (void)setBack:(BOOL)back
{
if (back) {
_frontBackLeftRight.bits |= TCJDirectionBackMask;
} else {
_frontBackLeftRight.bits &= ~TCJDirectionBackMask;
}
}
- (BOOL)isBack
{
return !!(_frontBackLeftRight.bits & TCJDirectionBackMask);
}
- (void)setLeft:(BOOL)left
{
if (left) {
_frontBackLeftRight.bits |= TCJDirectionLeftMask;
} else {
_frontBackLeftRight.bits &= ~TCJDirectionLeftMask;
}
}
- (BOOL)isLeft
{
return !!(_frontBackLeftRight.bits & TCJDirectionLeftMask);
}
- (void)setRight:(BOOL)right
{
if (right) {
_frontBackLeftRight.bits |= TCJDirectionRightMask;
} else {
_frontBackLeftRight.bits &= ~TCJDirectionRightMask;
}
}
- (BOOL)isRight
{
return !!(_frontBackLeftRight.bits & TCJDirectionRightMask);
}
@end
複製代碼
來咱們測試看一下是否正確,此次咱們依舊將front
設爲YES
、back
設爲NO
、left
設爲NO
、right
設爲YES
:
_frontBackLeftRight
聯合體只佔用一個字節,由於結構體中
front
、
back
、
left
、
right
都只佔一位二進制空間,因此結構體只佔一個字節,而
char
類型的
bits
也只佔一個字節.他們都在聯合體中,所以共用一個字節的內存便可. 並且咱們在
set
、
get
方法中的賦值和取值經過使用掩碼進行位運算來增長效率,總體邏輯也就很清晰了.可是若是咱們在平常開發中這樣寫代碼的話,極可能會被同事打死.雖然代碼已經很清晰了,可是總體閱讀起來仍是很吃力的.咱們在這裏學習了位運算以及聯合體這些知識,更多的是爲了方便咱們閱讀OC底層的代碼.下面咱們來回到本文主題,查看一下
isa_t
聯合體的源碼.
isa
它是一個聯合體,聯合體是一個結構佔8個字節,它的特性就是共用內存,或者說是
互斥,好比說若是
cls
賦值了就不在對
bits
進行賦值.在
isa_t
聯合體內使用宏
ISA_BITFIELD
定義了位域,咱們進入位域內查看源碼:
咱們看到,在內部分別定義了
arm64
位架構和
x86_64
架構的掩碼和位域.咱們只分析
arm64
爲架構下的部份內容(真機環境下). 能夠清楚的看到
ISA_BITFIELD
位域的內容以及掩碼
ISA_MASK
的值:
0x0000000ffffffff8ULL
.咱們重點看一下
uintptr_t shiftcls : 33;
,在
shiftcls
中存儲着類對象和元類對象的內存地址信息,咱們上文講到,對象的
isa
指針須要同
ISA_MASK
通過一次按位與運算才能得出真正的類對象地址.那麼咱們將
ISA_MASK
的值
0x0000000ffffffff8ULL
轉化爲二進制數分析一下:
從圖中能夠看到
ISA_MASK
的值轉化爲二進制中有33位都爲1,上文講到按位與運算是能夠取出這33位中的值.那麼就說明同
ISA_MASK
進行按位與運算就能夠取出類對象和元類對象的內存地址信息. 咱們繼續分析一下結構體位域中其餘的內容表明的含義:
至此咱們已經對
isa
指針有了新的認識,
arm64
架構以後,
isa
指針不僅僅只存儲了類對象和元類對象的內存地址,而是使用聯合體的方式存儲了更多信息,其中
shiftcls
存儲了類對象和元類對象的內存地址,須要同
ISA_MASK
進行
按位與 &
運算才能夠取出其內存地址值.
isa
是OC對象的第一個屬性,由於這一屬性是來自於繼承,要早於對象的成員變量,屬性列表,方法列表以及所遵循的協議列表. 在iOS底層原理探索 — alloc&init探索這篇文章中,當時咱們在探索對象的初始化的時候還有一個很是重要的點沒有細說就是:通過calloc
申請內存的時候,這個指針是怎麼和TCJPerson
這個類所關聯的呢? 下面咱們就能夠直接定位到:obj->initInstanceIsa(cls, hasCxxDtor)
obj
裏面只有一個指針對象與類直接的聯繫
initIsa(cls, true, hasCxxDtor)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = 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; } } 複製代碼
上面第一層判斷是isTaggedPointer
的斷言,這會在後續文章中重點分析
接下來是nonpointer
的判斷,由於nonpointer
優化,它是和普通結構不同的!經過上文咱們知道內存優化的isa_t
結構:它採用的是聯合體和位域的搭配.(目前咱們的類都是nonpointer
了)
nonpointer
,表明普通的指針,存儲着Class
、Meta-Class
對象的內存地址信息nonpointer
,則會進行一系列的初始化操做.其中的newisa.shiftcls = (uintptr_t)cls >> 3;
中的shiftcls
存儲着Class
、Meta-Class
對象的內存地址信息,咱們來驗證一下:
來咱們來對上面LLDB
相關的指令進行一波解析: 3. x/4gx obj
:表明打印obj
的4段內存信息 4. p/t
:表明打印二進制信息(還有p/o
、p/d
、p/x
分別表明八進制、十進制和十六進制打印) 5. p/t (uintptr_t)obj.class
將類信息進行二進制打印獲得:$3
6. 對第一個屬性isa
進行二進制打印p/t 0x001d8001000013f1
獲得:$1
7. 由於此時咱們是在x86_64
環境下進行打印的,經過上文咱們知道在x86_64
環境下isa
的ISA_BITFIELD
位域結構中:前3
位是nonpointer
,has_assoc
,has_cxx_dtor
,中間44
位是shiftcls
,後面17
位是剩餘的內容,同時由於iOS
是小端模式,那麼咱們就須要去掉右邊的3
位和左邊的17
位,因此就會採用$1>>3<<3
而後$4<<17>>17
的操做了.
經過這個測試,咱們就知道了isa
實現了對象與類之間的關聯. 在上文中咱們提獲得OC對象的isa
指針並非直接指向類對象或者元類對象的內存地址,而是須要& ISA_MASK
經過位運算才能獲取類對象或者元類對象的地址.來咱們也來驗證一波:
來咱們來對上面`LLDB`相關的指令進行一波解析:
1. 打印對象的內存信息:`x/4gx obj`
2. 打印類的信息:`p/x obj.class`獲得`$7`
3. 經過對象的`isa & ISA_MASK`操做:`p/x 0x001d8001000013f1 & 0x00007ffffffffff8ULL`獲得`$8`
4. 對比`$7`和`$8`他們是一模模同樣樣的
在此也驗證了`isa`實現了對象與類之間的關聯.
複製代碼
8字節指針
在64位
下 其實能夠存儲不少內容,咱們能夠優化內存,在不一樣的位上,放不一樣的東西! 在這咱們還須要補充一下Struct
與Union
的區別:
struct
和union
都是由多個不一樣的數據類型成員組成,但在任何同一時刻,union
中只存放了一個被選中的成員, 而struct
的全部成員都存在。在struct
中,各成員都佔有本身的內存空間,它們是同時存在的。一個struct
變量的總長度等於全部成員長度之和。在Union
中,全部成員不能同時佔用它的內存空間,它們不能同時存在。Union
變量的長度等於最長的成員的長度union
的不一樣成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於struct
的不一樣成員賦值是互不影響的.咱們都知道對象能夠建立多個,那麼類是否也能夠建立多個呢? 答案是一個.怎麼驗證它呢? 來咱們看下面代碼及打印結果:
經過運行結果證實了 類在內存中只會存在一份. 經過上面的打印,咱們發現類的內存結構裏面的第一個位置是:0x1000013f0-TCJPerson
他指向元類,是由系統建立的. 咱們來看一下對象-類-元類他們之間的關係:
到此咱們知道對象的isa
指向類,類的isa
指向元類,那麼元類的isa
指向哪呢?
接下來,咱們一塊兒來看一下 isa
的走位:
咱們在來看下面代碼打印:
經過運行結果咱們知道:元類-根元類-根根元類是一模模同樣樣的. 到此咱們用蘋果官方提供的一張圖看瞅一瞅: 咱們對上圖進行總結一波:圖中實線是 super_class指針,它表明着繼承鏈的關係.虛線是isa指針. 1.Root class (class)
其實就是
NSObject
,
NSObject
是沒有超類的,因此
Root class(class)
的
superclass
指向
nil
(
NSObject
父類是
nil
). 2.每一個
Class
都有一個isa指針指向惟一的
Meta class
. 3.
Root class(meta)
的
superclass
指向
Root class(class)
,也就是
NSObject
,造成一個迴路.這說明
Root class(meta)
是繼承至
Root class(class)
(根元類的父類是
NSObject
). 4.每一個
Meta class
的
isa
指針都指向
Root class (meta)
instance
對象的isa
指向class
對象class
對象的isa
指向meta-class
對象meta-class
對象的isa
指向基類的meta-class
對象在OC
中,類對象(class
對象)和元類對象(meta-class
對象)的本質結構都是struct objc_class
指針,即在內存中就是結構體
Class clas = [NSObject class];
複製代碼
來到class
底層源碼,咱們能夠看到:
typedef struct objc_class *Class;
複製代碼
class
對象實際上是一個objc_class
結構體的指針.所以咱們能夠說類對象或元類對象在內存中其實就是objc_class
結構體. 來咱們來看一下源碼:
objc_class
結構體繼承
objc_object
而且結構體內有一些函數,由於這是
c++
結構體,在
C
的基礎之上作了擴展.所以結構體中能夠包含函數.注意觀察註釋掉的
Class ISA
這一行代碼. 咱們來到
objc_object
內,繼續截取部分代碼:
咱們發現
objc_object
中有一個
isa
指針,那麼
objc_class
繼承
objc_object
,也就一樣擁有一個
isa
指針。繼承來了
isa
指針,因此上文咱們提到了
Class ISA
也就被註釋掉了. 再來看第二行代碼
Class superclass
:咱們來打印一下來看結果:
也就是說
objc_class
內存中第一個位置是
isa
,第二個位置是
superclass
,其餘位置咱們後續文章在分析.到此,咱們要怎樣繼續進行分析呢? 咱們都知道咱們平時編寫的
Objective-C
代碼,其底層的實現都是
C/C++
代碼.
因此
Objective-C
的面向對象都是基於
C/C++
的數據結構實現的.那麼
Objective-C
的對象、類主要是基於
C/C++
的什麼數據結構實現的呢?--結構體. 所以,咱們能夠經過將建立好的
OC
文件,轉化爲
C++
文件來看一下
OC
對象的底層結構.
OC
代碼轉換爲C/C++
代碼在iOS底層原理探索 — 內存對齊&malloc源碼分析一文中,咱們提到過若是將OC
代碼轉化爲C/C++
了,這裏咱們在複習一下: 經過命令行將OC的main.m文件轉換成C++文件,生成main.cpp.
clang -rewrite-objc main.m -o main.cpp
/***rewrite表明 重寫
*-o表明 輸出
*cpp表明 c++(c plus plus)
**/
複製代碼
須要注意這種方式沒有指定運行平臺和架構模式,咱們能夠經過命令行設置參數,來指定運行平臺和架構模式
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
/***xcrun表明 xcode
* iphoneos表明 運行在iPhone上
*-arch表明 架構模式,arm64參數表明64位的架構模式
**/
複製代碼
生成的main.cpp 文件就是main.m轉換c++後的文件,直接拖拽到工程中,就能夠查看底層實現了. 咱們的OC
文件爲main.m
:
C++
文件
main.cpp
後,咱們在
main.cpp
文件中搜索
TCJPerson
,能夠找到
TCJPerson_IMPL
(
IMPL
即
implementation
的縮寫,表明實現).
經過上文咱們能夠看到:
NSObject
的底層實現就是一個結構體.Class
其實就是一個指針,指向了objc_class
類型的結構體.TCJPerson_IMPL
結構體內有三個成員變量:
isa
繼承自父類NSObject
helloName
_name
name
:底層編譯會生成相應的setter
、getter
方法,且幫咱們轉化爲_name
helloName
:底層編譯不會生成相應的setter
、getter
方法,且沒有轉化爲_helloName
接下來咱們來看看main.cpp
文件中的method_list_t
:
(struct objc_selector *)"name"
對應
SEL
,
"@16@0:8"
對應的就是方法簽名,
(void *)_I_TCJPerson_name
對應的方法實現(即
IMP
). 其中的
"@16@0:8"
方法簽名中對應一個返回值和兩個參數: 1.
@
返回值類型:
id
返回16,表明總共的量 2.
@
參數一類型:
id
0-7 3.
:
參數二類型:
sel
8-15
咱們來打印一下@
、 :
具體表明啥: