??我們這里討論類的結(jié)構(gòu)拣挪,我們先定義2個類Strudent
和Person
逼庞,Strudent
繼承自Person
,Person
繼承自NSObject
闺鲸。
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
# define ISA_MASK 0x00007ffffffffff8ULL
@interface Person : NSObject{
NSString *nickname;
}
@property (nonatomic, copy)NSString *name;
-(void)eat;
+(void)drink;
@end
@implementation Person
-(void)eat{
NSLog(@"eat");
}
+(void)drink{
NSLog(@"drink");
}
@end
@interface Student : Person
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Student *student = [[Student alloc]init];
Person *person = [[Person alloc]init];
NSLog(@"%@ - %@",student,person);
}
return 0;
}
我們先用lldb
調(diào)試,看看類的在內(nèi)存中的地址埃叭。
我們可以看到摸恍,
p/x 0x001d8001000022e5 & 0x00007ffffffffff8ULL
和p/x 0x00000001000022b8 & 0x00007ffffffffff8ULL
打印的結(jié)果一致。這是為什么呢赤屋?因?yàn)?code>0x00000001000022e0是示例對象isa
經(jīng)過掩碼計(jì)算后得出的類對象的地址立镶,而0x00000001000022b8
是類對象的isa
經(jīng)過掩碼計(jì)算后的元類對象的地址,元類是iOS底層一個抽象的概念类早,由編譯器自動完成媚媒,所以兩個結(jié)果是相同的。
元類
1.實(shí)例對象中存放成員變量涩僻,實(shí)例對象的
isa
指向類對象
2.類對象中存放實(shí)例方法缭召,類對象的isa
指向元類對象
3.元類對象存放類方法,元類對象的isa
指向根元類NSObject
我們可以繼續(xù)往下進(jìn)行lldb
的調(diào)試逆日,得到根類NSObject
嵌巷,lldb
的說明和我們上面的一樣。
然后我們打印
NSObject
室抽,這里的兩個地址不一樣搪哪,為什么?難道是因?yàn)榈讓佑辛硗庖粋€NSObject
對象嗎坪圾?我們接下來驗(yàn)證一下晓折。 Class class1 = [Person class];
Class class2 = [Person alloc].class;
Class class3 = object_getClass([Person alloc]);
Class class4 = [Person alloc].class;
NSLog(@"%p",class1);
NSLog(@"%p",class2);
NSLog(@"%p",class3);
NSLog(@"%p",class4);
打印結(jié)果:
這里說明在內(nèi)存中惑朦,所有的類對象只會創(chuàng)建一份,為什么NSObject對象的地址會不一樣呢漓概。我們繼續(xù)lldb調(diào)試漾月。
這里一樣了,因?yàn)槲覀儎偛挪灰粯拥脑蚴且粋€是
NSObject
對象垛耳,一個是NSObject
的元類對象栅屏。大家明白了嗎?然后我們看看經(jīng)典的isa走位圖堂鲜,這個圖片網(wǎng)上都有栈雳,因?yàn)楹芙?jīng)典,所以大家都在用缔莲。objc_class & objc_object
??為什么對象哥纫,類,元類痴奏,都有isa
呢蛀骇?我們查看源碼,里面有一個類型读拆。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
??然后我們查看發(fā)現(xiàn)有一個繼承自他的類
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
??說白了擅憔,我們的NSObject
對象只是OC
幫我們封裝后的記過,在底層C/C++
的實(shí)現(xiàn)里檐晕,是沒有對象的概念的暑诸,在底層類都是struct objc_class
類型的,然后繼承自objc_object
(結(jié)構(gòu)體)辟灰。
上面我們說到了个榕,實(shí)例方法存在類對象里,類方法存在元類里芥喇,那么我們怎么驗(yàn)證呢西采?
看上方源碼里的類結(jié)構(gòu),第一個是被注釋掉的
//Class ISA
继控,因?yàn)槲覀兪怯辛死^承的ISA
械馆,第二個是superclass
(即NSObject
),如果是那么我們打印的第二串地址里0x00000001000022d0
湿诊,應(yīng)該存放的是我們的父類信息狱杰。看下圖厅须,我們得到了驗(yàn)證結(jié)果仿畸,是這樣的。接下來我們先補(bǔ)充一段內(nèi)存偏移的知識,這樣我們才能一步步拿到類后面的信息错沽。內(nèi)存偏移
int a = 10;
int b = 10;
NSLog(@"%d----%p",a,&a);
NSLog(@"%d----%p",b,&b);
輸出結(jié)果:ab的內(nèi)存地址差了4個字節(jié)簿晓。我們再來看數(shù)組指針,
//數(shù)組指針
int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
NSLog(@"%p -- %p - %p", d, d+1, d+2);
輸出結(jié)果:從打印結(jié)果我們知道:
-
&c
和&c[0]
都是取 首地址千埃,即數(shù)組名等于首地址憔儿,所以相同。 -
&c
與&c[1]
相差4個字節(jié)放可,地址之間相差的字節(jié)數(shù)谒臼,主要取決于存儲的數(shù)據(jù)類型 - 可以通過 首地址+偏移量取出數(shù)組中的其他元素,其中偏移量是數(shù)組的下標(biāo)耀里,內(nèi)存中首地址
實(shí)際移動的字節(jié)數(shù)等于 偏移量 x 數(shù)據(jù)類型字節(jié)數(shù)
所以剛才我們打印出來的NSObject
就是根據(jù)內(nèi)存偏移得出來的蜈缤,那么接下來我們想要知道類里面的bits
信息,我們只需要知道cache
的大小冯挎,然后讓內(nèi)存偏移就行了底哥。剛才的結(jié)果可不是蒙的哦~
計(jì)算cache類的內(nèi)存大小
進(jìn)入cache
類cache_t
的定義(只貼出了結(jié)構(gòu)體中非static
修飾的屬性,主要是因?yàn)?code>static類型的屬性不存在結(jié)構(gòu)體的內(nèi)存中)房官,有如下幾個屬性
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 是一個結(jié)構(gòu)體指針類型趾徽,占8字節(jié)
explicit_atomic<mask_t> _mask; //是mask_t 類型,而 mask_t 是 unsigned int 的別名翰守,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; //是指針蛮拔,占8字節(jié)
mask_t _mask_unused; //是mask_t 類型舷丹,而 mask_t 是 uint32_t 類型定義的別名锄蹂,占4字節(jié)
#if __LP64__
uint16_t _flags; //是uint16_t類型狼牺,uint16_t是 unsigned short 的別名,占 2個字節(jié)
#endif
uint16_t _occupied; //是uint16_t類型事示,uint16_t是 unsigned short 的別名,占 2個字節(jié)
計(jì)算前兩個屬性的內(nèi)存大小僻肖,有以下兩種情況肖爵,最后的內(nèi)存大小總和都是12字節(jié)
【情況一】if流程
-
buckets
類型是struct bucket_t *
,是結(jié)構(gòu)體指針類型臀脏,占8字節(jié) -
mask
是mask_t
類型劝堪,而mask_t
是unsigned int
的別名,占4字節(jié)
【情況二】elseif流程
-
_maskAndBuckets
是uintptr_t類型揉稚,它是一個指針秒啦,占8字節(jié) -
_mask_unused
是mask_t
類型,而mask_t
是uint32_t
類型定義的別名搀玖,占4字節(jié) -
_flags
是uint16_t
類型余境,uint16_t
是unsigned short
的別名,占 2個字節(jié) -
_occupied
是uint16_t
類型,uint16_t
是unsigned short
的別名芳来,占 2個字節(jié)
所以最后計(jì)算出cache類的內(nèi)存大小 = 12 + 2 + 2 = 16字節(jié)含末。
接下來我們就來重點(diǎn)了,獲取bits
即舌。所有的內(nèi)容我們只需要首地址偏移32字節(jié)即可佣盒。然后看lldb
調(diào)試(下圖)。(bits
的類型class_data_bits_t
)
注: x/4gx
我們拿到Person
類的首地址0x100002138+32 = 0x100002158
(16進(jìn)制)-
p $1->data()
是因?yàn)?code>OC底層有提供bits
的data()
方法,我們可以看到方法列表的類型是class_rw_t
(class_rw_t類型圖如下)
class_rw_t
我們繼續(xù)lldb
調(diào)試顽聂,打印其中的屬性列表肥惭,方法列表。
但是屬性好像只有一個
@"nickname"
紊搪,但是我們看看我們定義的屬性蜜葱。
@interface Person : NSObject{
NSString *name;
}
@property (nonatomic, copy)NSString *nickname;
-(void)eat;
+(void)run;
@end
明明有兩個,那么name
這個成員變量跑哪里去了呢嗦明?為什么property_list
中只有屬性笼沥,沒有成員變量呢?
探索成員變量的存儲位置
在剛才我們查看class_rw_t
的類型的時候娶牌,我們發(fā)現(xiàn)了methods()奔浅,properties(),protocols()
诗良,然后在網(wǎng)上汹桦,我們還有一個類型沒有注意到class_ro_t
(如下圖)
那么我們是不是就可以猜測,這里存放的是成員變量呢鉴裹?我們繼續(xù)
lldb
調(diào)試舞骆。在里面,我們成功找到了
name
径荔。知道了屬性和成員變量的存儲位置督禽,那么接下來我們探討方法的存儲。
探索方法列表methods_list
剛才我們lldb
調(diào)試的是properities()
,這次我們用methods()
总处。
我們成功的找到了實(shí)例方法狈惫,
eat()
,我們繼續(xù)往下看看方法列表里都放了什么方法鹦马。go on lldb
我們找到了很多方法胧谈,比如
eat(),cxx_destruct(),nickname
的getter
和setter
方法。但是好像沒有我們上面自己定義的類方法荸频,run()
菱肖。所以他應(yīng)該不存在這里。很簡單旭从,我們驗(yàn)證我們上面的說法稳强,究竟是不是放在元類里呢场仲?繼續(xù)lldb
唄?還能咋地键袱?OK燎窘,看到了沒,類方法已經(jīng)被我們找到了蹄咖。接下來我們來總結(jié)一下褐健。
總結(jié):
objc_object是我們OC底層實(shí)現(xiàn)對象的基類,里面重要的數(shù)據(jù)類型就是澜汤,
Class ISA蚜迅,Class superclass,cache_t cache俊抵,class_data_bits_t bits,
重要的信息比如屬性列表谁不,方法列表,協(xié)議列表都放在bits
這里徽诲。通過
{}
定義的屬性沒有set
和get
方法刹帕,存放在bits --> data() -->ro() --> ivars
獲取成員變量列表通過
@ property
定義的屬性,存放在bits --> data() -->() --> list
獲取成員屬性列表方法在底層的類型是
class_rw_t
類型谎替,在class_rw_t
的實(shí)現(xiàn)內(nèi)部偷溺,我們又發(fā)現(xiàn)了類方法的類型是class_ro_t
的類型。類的實(shí)例方法存儲在類的
bits
屬性中钱贯,通過bits --> methods() --> list
獲取實(shí)例方法列表挫掏,例如Person
類的實(shí)例方法eat
就存儲在Person
類的bits
屬性中,類中的方法列表除了包括實(shí)例方法秩命,還包括屬性的set
方法和get
方法類的類方法存儲在元類的
bits
屬性中尉共,通過元類bits --> methods() --> list
獲取類方法列表,例如Person
中的類方法run
就存儲在Person類的元類(名稱也是Person)的bits
屬性中