iOS開發(fā)之類的本質(zhì)

??我們這里討論類的結(jié)構(gòu)拣挪,我們先定義2個類StrudentPerson逼庞,Strudent繼承自PersonPerson繼承自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 & 0x00007ffffffffff8ULLp/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)證一下晓折。
image.png

        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)典,所以大家都在用缔莲。
isa流程圖.png

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)入cachecache_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é)
  • maskmask_t類型劝堪,而mask_tunsigned int的別名,占4字節(jié)

【情況二】elseif流程

  • _maskAndBuckets是uintptr_t類型揉稚,它是一個指針秒啦,占8字節(jié)
  • _mask_unusedmask_t類型,而mask_tuint32_t類型定義的別名搀玖,占4字節(jié)
  • _flagsuint16_t類型余境,uint16_tunsigned short的別名,占 2個字節(jié)
  • _occupieduint16_t類型,uint16_tunsigned 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底層有提供bitsdata()方法,我們可以看到方法列表的類型是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(),nicknamegettersetter方法。但是好像沒有我們上面自己定義的類方法荸频,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這里徽诲。

  • 通過{}定義的屬性沒有setget方法刹帕,存放在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屬性中

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弃锐,一起剝皮案震驚了整個濱河市袄友,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌霹菊,老刑警劉巖杠河,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浇辜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)唾戚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門柳洋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叹坦,你說我怎么就攤上這事熊镣。” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵绪囱,是天一觀的道長测蹲。 經(jīng)常有香客問我,道長鬼吵,這世上最難降的妖魔是什么扣甲? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮齿椅,結(jié)果婚禮上琉挖,老公的妹妹穿的比我還像新娘。我一直安慰自己涣脚,他們只是感情好示辈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遣蚀,像睡著了一般矾麻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芭梯,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天险耀,我揣著相機(jī)與錄音,去河邊找鬼粥帚。 笑死胰耗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芒涡。 我是一名探鬼主播柴灯,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼费尽!你這毒婦竟也來了赠群?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤旱幼,失蹤者是張志新(化名)和其女友劉穎查描,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柏卤,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冬三,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缘缚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勾笆。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖桥滨,靈堂內(nèi)的尸體忽然破棺而出窝爪,到底是詐尸還是另有隱情弛车,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布蒲每,位于F島的核電站纷跛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏邀杏。R本人自食惡果不足惜贫奠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淮阐。 院中可真熱鬧叮阅,春花似錦、人聲如沸泣特。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽状您。三九已至勒叠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膏孟,已是汗流浹背眯分。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柒桑,地道東北人弊决。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像魁淳,于是被迫代替她去往敵國和親飘诗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345