這篇文件來探索類的底層c++
一個自定義類的類名是咱們決定的,因此咱們想進行類探索,就得看看咱們定義的類,在底層是如何被定義的。緩存
先準備一個自定義類DZPerson
,在類中定義一個成員變量(_nick),一個屬性(nameStr)。實現了一個方法(-saySomethine)和一個類方法(+sayHello)markdown
@interface DZPerson : NSObject
{
NSString *_nick;
}
@property (copy, nonatomic) NSString *nameStr;
@end
@implementation DZPerson
- (void)saySomething {
NSLog(@"%s", __func__);
}
+ (void)sayHello {
NSLog(@"%s", __func__);
}
@end
複製代碼
在main中進行初始化。函數
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
DZPerson *person = [DZPerson alloc];
}
return 0;
}
複製代碼
使用終端,進入到main.m
文件所在的路徑,使用clang
命令,編譯main.m
文件ui
clang -rewrite-objc main.m -o main.cpp
複製代碼
輸出main.cpp,打開這個文件,在裏面進行搜索DZPerson
,做爲線索的出發點:atom
typedef struct objc_object DZPerson;
⏬⏬⏬
struct DZPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_nick;
};
⏬⏬⏬
struct NSObject_IMPL {
Class isa;
};
⏬⏬⏬
typedef struct objc_class *Class;
複製代碼
struct objc_object
結構體的別名。struct DZPerson_IMPL
結構體,包含兩個成員屬性:
struct NSObject_IMPL
,經過名字中的字段,瞭解到這個應該是父類NSObject
_nick
NSObject_IMPL
結構體,裏面有一個成員isa
,是Class
類型。Class
是struct objc_class *
結構體指針類型。經過以上邏輯,咱們的目標轉移到struct objc_object
和struct objc_class
這兩個結構體上了。spa
看看這兩個結構體在源碼中的定義,代碼比較長,我只截獲了須要咱們研究的部分:3d
struct objc_object {
private:
isa_t isa;
public:
//這裏定義了一些函數,先省略
...
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
//省略後續代碼
...
}
複製代碼
objc_object
中只有一個成員:isa
objc_class
繼承自objc_object
:
objc_object
中的isa(蘋果的註釋仍是挺人性的)經過以上的源碼查看和相關的簡要分析,咱們能夠確認自定義類
在底層會被轉化成objc_class
類型的結構體。實例對象在底層就是objc_object
類型。經過下面的兩行源碼,能夠直接證實:指針
typedef struct objc_class *Class;
typedef struct objc_object *id;
複製代碼
此時看文章的你可能會有個疑惑:objc_class
繼承自objc_object
,==那麼類也是對象?==調試
OC中類也是對象,如何證實呢?咱們知道,實例對象中的isa指向的是類,類中也有isa,咱們經過lldb來看看類的isa: 咱們以文章開頭的代碼爲例:
DZPerson *person = [DZPerson alloc];
複製代碼
lldb執行截圖:
x/4gx perosn
:首地址中的第一個值是person對象的isa。DZPerosn
,地址是:0x0000000100002378
0x0000000100002378
的內存狀況,也就是查看DZPerson
類的內存DZPerson
的isa
,並po一下,打印的結果頁是DZPerson
,可是地址和前面打印的地址不一樣。這個第二個DZPerosn
是元類
。NSObject
,可是它不是NSObject
類,而是NSObject
的元類,也就是根元類
。而根元類
的isa指向仍是根元類
簡單來講,==實例對象isa指向類,類isa指向元類,元類isa指向根元類,根元類的isa指向根元類。==經過一張圖,能夠更好的理解isa的走位指向: 圖中虛線表明isa走位,實現表明superclass走位。
==所以:類是元類的實例對象,而元類是根元類的示例對象。因此說,類也是對象,萬物接對象==。
接下來咱們用lldb來看看類中存儲的屬性、方法,前文說到這些信息都存儲在class_data_bits_t bits
中,使用指針偏移的方式就能夠獲取到bits。 此處放一張objc_class的源碼截圖: 拿到class在內存中的首地址,分別加上屬性isa、superclass和cache佔用的內存大小,就能夠獲得bits的地址。
isa
和superclass
都是指針類型,因此分別佔用8字節。
那麼如今須要計算cache佔用多少字節,看看cache_t
的源碼:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
//省略靜態成員變量
...
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
public:
//省略後面的函數
...
}
複製代碼
mask_t
類型。指針佔用8個字節,mask_t
是uint32_t
的別名,也就是佔中4個字節。_flags
和_occupied
分別佔用2個字節,由於是uint16_t
類型。cache_t cache
成員在結構體中佔用16字節==那麼想要獲得class_data_bits_t bits
的偏移量,就是isa+superclass+cache=8+8+16=32,轉換成十六進制就是20==
條件知足,咱們開始打印類中的信息:
DZPerson
的首地址首地址的第一個元素就是類的isa,因此咱們直接以十六進制打印便可。
獲取bits
,拿到首地址進行+20偏移,20是前面計算出來的(0x0000000100002398 = 0x0000000100002378 + 20)。而且進行強制類型轉換
調用class_rw_t *data() const
函數(前文中,objc_class
源碼中的函數),獲得地址,打印地址中的值:
接下來用class_rw_t
中的methods()
函數,能夠獲取到方法列表:
list
函數獲取列表的首地址,打印地址的值後,用get查看到類中的全部方法。經過下圖能看到類中定義的saySomething
方法、屬性nameStr的setter和getter方法,還有一個系統生成的.cxx_destruct
方法:
+ (void)sayHello
,在這裏是看不到的。由於類方法存在元類中。經過上面的方式咱們就能夠產看到類中方法,也能夠證實,方法是存在類中。一樣,經過class_rw_t
中的properties()
能夠獲取到屬性、protocols()
獲取協議。
成員變量存在ro
中,經過函數ro()
獲取:
objc_class
,而且知道類中也存在isa指針。但願本文能對你有所幫組,若是有些的很差的地方,還請指出,謝謝!