iOS底層原理_04:類的原理分析(上)

第四節(jié)課 類的原理分析(上)

isa分析到元類

p/x p  //拿到當(dāng)前指針地址

04-拿地址.png

我們通過x p指令可以驗(yàn)證下唱矛,看到打印出的地址與我們拿到的地址一模一樣。

接下來我們
x/4gx 0x000000010292f480

04-isa.png

0x001d800100008365 就是我們的對象的isa

p/x 0x001d800100008365 & 0x00007ffffffffff8 po 0x0000000100008360 //就是我們當(dāng)前的類

04-當(dāng)前的類.png

其實(shí)最后還是這么個(gè)地址缸榛,有那么一丟丟好奇朱沃,再次x/4g會得到什么,干就完了~

04-x:4g類.png

可以看到耍铜,打印出來的結(jié)果里跟上面的指針對象擁有同樣的內(nèi)存結(jié)構(gòu)塞赂,那第一個(gè)地址是啥泪勒?還是isa嘛?
04-po.png

又一次得到LGPerson,0x0000000100008360LGPerson0x0000000100008338還是LGPerson圆存,小朋友叼旋,你是否有很多問號?沦辙?
猜想:類會和我們的對象無限開辟夫植,內(nèi)存不止有一個(gè)類
接下來就來驗(yàn)證下我們的猜想

    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\n%p \n%p \n%p \n%p",class1,class2,class3,class4);
    
    <----輸出結(jié)果---->
0x100008360 
0x100008360 
0x100008360 
0x100008360

我們可以看到最終輸出的類都是0x100008360,那就證明之前的0x0000000100008338它不是類油讯,那么它是個(gè)什么详民?接下來我們就需要使用爛蘋果來分析了

04-爛蘋果.png

在這里我們看到了熟悉的東西,但是并沒有看到0x0000000100008338這個(gè)東西

當(dāng)我們看到符號表的時(shí)候看到了一個(gè)奇奇怪怪的東西


04-METACLASS.png

這個(gè)METACLASS實(shí)際上并不是我們通過代碼創(chuàng)建的撞羽,這個(gè)是系統(tǒng)或者編譯器幫我們生成好的阐斜,這個(gè)東西就叫做元類
我們得到了一個(gè)大概的流程:對象isa -> 類isa -> 元類

isa走位圖和繼承鏈

isa走位圖

上面我們通過isa分析出了一個(gè)元類诀紊,那我們就想,元類之后會不會還有東西隅俘?
繼續(xù)用上面元類的isa

04-根元類.png

po打印輸出的是NSObject邻奠,&上我們的掩碼,又是他本身为居,這個(gè)東西就是我們的根元類
對象isa -> 類isa -> 元類 -> 根元類
我們看到了NSObject有點(diǎn)奇怪碌宴,那我們就跟直接打印的對比下
04-NSObject.png

我們發(fā)現(xiàn),通過NSObject.class獲取到的地址與剛才po的地址并不相同蒙畴,但是我們繼續(xù)往下x/4g的時(shí)候發(fā)現(xiàn)isa的位置又與po的地址相同了贰镣。就相當(dāng)于是NSObject.class的isa -> NSObject的元類,只有兩層
根類 isa -> 根元類 isa
04-isa流程圖.png

根據(jù)上面我們分析的過程可以看到isa的鏈路如上圖虛線部分膳凝。同時(shí)我們也可以使用代碼來進(jìn)行驗(yàn)證一下

    NSObject *object1 = [NSObject alloc];
    // NSObject類
    Class class = object_getClass(object1);
    // NSObject元類
    Class metaClass = object_getClass(class);
    // NSObject根元類
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject根根元類
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 實(shí)例對象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    // LGPerson元類
    Class pMetaClass = object_getClass(LGPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"%@ - %p",psuperClass,psuperClass);
    
    
<----輸出結(jié)果---->
0x100547790 實(shí)例對象
0x7fff9103a118 類
0x7fff9103a0f0 元類
0x7fff9103a0f0 根元類
0x7fff9103a0f0 根根元類

NSObject - 0x7fff9103a0f0

繼承鏈

根據(jù)上面的打印結(jié)果碑隆,我們發(fā)現(xiàn)任何對象,元類的父類都是根元類蹬音,有點(diǎn)燒腦了吧上煤?
接下來我們寫了一個(gè)LGTeacher繼承自LGPerson,再次通過上面代碼進(jìn)行打印

    // LGTeacher -> LGPerson -> NSObject
    // 元類也有一條繼承鏈
    Class tMetaClass = object_getClass(LGTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"%@ - %p",tsuperClass,tsuperClass);
    <----輸出結(jié)果---->
    LGPerson - 0x100008338

元類的繼承鏈如下圖實(shí)線部分
[圖片上傳失敗...(image-743b09-1624451069459)]

我們繼續(xù)分析NSObject特殊情況

    // NSObject 根類特殊情況
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"%@ - %p",nsuperClass,nsuperClass);
    // 根元類 -> NSObject
    Class rnsuperClass = class_getSuperclass(metaClass);
    NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
    <----輸出結(jié)果---->
(null) - 0x0
 NSObject - 0x7fff9103a118

根據(jù)上述結(jié)果著淆,我們推出了圖中RootClass(class) -> nil的這一步
以及RootClass(meta) -> RootClass(class)這一步

小結(jié):

  1. NSOject對象的元類根元類同一個(gè)
  2. 元類間存在著繼承的關(guān)系劫狠,跟類是一樣的
  3. 根元類的父類指向了NSObject
  4. NSObject的父類是(null),地址為0x0永部,即NSObject沒有父類

指針和內(nèi)存平移

如果要想獲取對象內(nèi)存中的變量独泞,底層實(shí)現(xiàn)方式是對象的首地址+偏移值。下面探究下內(nèi)存偏移
我們平時(shí)遇到的指針分為三種:普通指針苔埋、對象指針懦砂、數(shù)組指針

普通指針

int a = 10; 
int b = 10; 
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
<----輸出結(jié)果---->
10 -- 0x7ffeefbff41c
10 -- 0x7ffeefbff418
  • 我們發(fā)現(xiàn),值是一樣的,但是指針地址卻是不同的孕惜,這就是我們所說的copy:值拷貝
  • a的地址是0x7ffeefbff41c愧薛,b的地址是0x7ffeefbff418,相差4字節(jié)衫画,主要取決于a的類型

對象指針

HZMPerson *p1 = [HZMPerson alloc];
HZMPerson *p2 = [HZMPerson alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
<----輸出結(jié)果---->
<HZMPerson: 0x1007049f0> -- 0x7ffeefbff410
<HZMPerson: 0x100704650> -- 0x7ffeefbff408

我們發(fā)現(xiàn)毫炉,對象的地址不同,地址指向的空間也不同

數(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é)果---->
0x7ffeefbff430 - 0x7ffeefbff430 - 0x7ffeefbff434
0x7ffeefbff430 - 0x7ffeefbff434 - 0x7ffeefbff438

  • 數(shù)組的地址就是數(shù)組元素中的首地址削罩,即&c&c[0]都是首地址
  • 數(shù)組中每個(gè)元素之間的地址間隔瞄勾,由當(dāng)前元素的數(shù)據(jù)類型決定
  • 數(shù)組的元素地址可以通過首地址+n*類型大小方式,這種方式是數(shù)組中的元素類型必須相同弥激。
  • 數(shù)組元素不相同用首地址+偏移量方式进陡,根據(jù)當(dāng)前變量的偏移值(需要前面類型大小相加)

小結(jié):

  • 內(nèi)存地址就是內(nèi)存元素的首地址
  • 內(nèi)存偏移可以根據(jù)首地址+ 偏移值方法獲取相對應(yīng)變量的地址

源碼分析類的結(jié)構(gòu)

在之前的探索時(shí)我們發(fā)現(xiàn)isaClass類型的。Class類型是objc_class * 微服,objc_class是一個(gè)結(jié)構(gòu)體趾疚。 所有的Class底層實(shí)現(xiàn)都是objc_class。又來到了我們的源碼分析部分以蕴,在 objc4-818.2 中全局搜索objc_class 代碼如下

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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 getSuperclass() const {
#if __has_feature(ptrauth_calls)
#   if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
        if (superclass == Nil)
            return Nil;
            
//省略部分源碼
}

objc_class這個(gè)結(jié)構(gòu)體內(nèi)部存儲了幾個(gè)成員變量糙麦,怎么去探究它們呢?類的地址是知道的丛肮,那么就根據(jù)上面探究過的首地址+偏移值來獲取里面的成員變量的地址赡磅,然后獲取值。但是偏移值需要知道當(dāng)前變量之前的所有成員變量的大小

  • Class ISA://結(jié)構(gòu)體指針占8字節(jié) 繼承自objc_object
  • Class superclass:結(jié)構(gòu)體指針占8字節(jié)
  • cache_t cache:宝与?焚廊?母雞
  • class_data_bits_t bits;

計(jì)算 cache 類的內(nèi)存大小

接下來我們就先看cache的內(nèi)存大小,查看cache_t

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;   // 4
#if __LP64__
            uint16_t                   _flags;       // 2
#endif
            uint16_t                   _occupied;    // 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
    };
    
    //下面是一些方法省略
};

cache_t結(jié)構(gòu)體類型习劫,有兩個(gè)成員變量_bucketsAndMaybeMask和一個(gè)聯(lián)合體

_bucketsAndMaybeMaskuintptr_t無符長整型占8字節(jié)

聯(lián)合體里面有兩個(gè)成員變量結(jié)構(gòu)體_originalPreoptCache咆瘟,聯(lián)合體的內(nèi)存大小由成員變量中的最大變量類型決定
_originalPreoptCache 是結(jié)構(gòu)體指針占8字節(jié)
結(jié)構(gòu)體中有_maybeMask_flags榜聂、_occupied搞疗。 _maybeMaskuint32_t4字節(jié)_flags_occupieduint16_t 各占2字節(jié)须肆,結(jié)構(gòu)體大小是8字節(jié)

cache_t的內(nèi)存大小是 8+8 或者是8+4+2+2都是16字節(jié)匿乃,所以cache就是16字節(jié)

*isa的內(nèi)存地址是首地址,前面已經(jīng)探究了

  • superclass的內(nèi)存地址是首地址+0x8
  • cache的內(nèi)存地址是首地址+0x10
  • bits的內(nèi)存地址是首地址+0x20

具體cache里面的內(nèi)容我們后續(xù)再進(jìn)行講解豌汇,我們先看看bits這個(gè)東西

獲取bits

所以有上述計(jì)算可知幢炸,想要獲取bits的中的內(nèi)容,只需通過首地址平移32字節(jié)即可

以下是通過lldb命令調(diào)試的過程


04-bits.png

ps:獲取類的首地址有兩種方式

  1. 通過p/x LGPerson.class直接獲取首地址
  2. 通過x/4gx LGPerson.class拒贱,打印內(nèi)存信息獲取

$2指針的打印結(jié)果中可以看出bits中存儲的信息宛徊,其類型是class_rw_t佛嬉,也是一個(gè)結(jié)構(gòu)體類型。但我們還是沒有看到屬性列表闸天、方法列表等暖呕,需要繼續(xù)往下探索

探索 屬性列表,即 property_list

通過查看class_rw_t定義的源碼發(fā)現(xiàn)苞氮,結(jié)構(gòu)體中有提供相應(yīng)的方法去獲取 屬性列表湾揽、方法列表等,如下所示

04-class_rw_t.png

在獲取bits并打印bits信息的基礎(chǔ)上笼吟,通過class_rw_t提供的方法库物,繼續(xù)探索 bits中的屬性列表,以下是lldb探索的過程圖示

04-獲取屬性列表2.png

  • p $3.properties()命令中的propertoes方法是由class_rw_t提供的,方法中返回的實(shí)際類型為property_array_t

  • 由于list的類型是property_list_t贷帮,是一個(gè)指針戚揭,所以通過 p *$6獲取內(nèi)存中的信息,同時(shí)也證明bits中存儲了 property_list撵枢,即屬性列表

  • p $7.get(2)民晒,想要獲取LGPerson中的下一個(gè)成員變量,發(fā)現(xiàn)提示數(shù)組越界了锄禽,說明 property_list 中只有兩個(gè)屬性

目前我們的LGPerson中只有兩個(gè)屬性镀虐,那么我們添加一些其他的東西后,property_list會有什么變化呢沟绪?

04-新增成員變量.png

我們新增了一個(gè)成員變量,重復(fù)上面步驟結(jié)果發(fā)現(xiàn)空猜,還是只能取出兩個(gè)屬性绽慈,由此可得出property_list 中只有屬性,沒有成員變量與方法辈毯。那成員變量哪去了坝疼?

通過查看objc_classbits屬性中存儲數(shù)據(jù)的類class_rw_t的定義發(fā)現(xiàn),除了methods谆沃、properties钝凶、protocols方法,還有一個(gè)ro方法唁影,其返回類型是class_ro_t耕陷,通過查看其定義,發(fā)現(xiàn)其中有一個(gè)ivars屬性据沈,我們大膽猜測:是否成員變量就存儲在這個(gè)ivar_list_t類型的ivars屬性中呢哟沫?下圖是lldb命令的調(diào)試流程

04-成員變量.png

通過{}定義的成員變量,會存儲在類的bits屬性中锌介,通過bits -> data() ->ro() -> ivars獲取成員變量列表嗜诀,除了包括成員變量猾警,還包括屬性定義的成員變量
通過@property定義的屬性,也會存儲在bits屬性中隆敢,通過bits -> data() -> properties() -> list獲取屬性列表发皿,其中只包含屬性

探索methods_list

上面我們新增了成員變量,現(xiàn)在我們添加兩個(gè)方法來看看


04-新增方法.png

繼續(xù)上面的lldb調(diào)試


04-methodslist.png

但是我們發(fā)現(xiàn)直到我們?nèi)〉皆浇绶餍蛴〕鰜淼姆椒ǘ际强盏姆椒ㄑㄊ@是為啥?
這個(gè)其實(shí)是因?yàn)?br> property_t的底層是存在兩個(gè)成員變量的

struct property_t {
    const char *name;
    const char *attributes;
};

而我們的method_t則沒有

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    }; 

但是我們可以看見 有一個(gè)結(jié)構(gòu)體big匣屡,里面則存在著成員變量name,我們?nèi)∫幌略囋?br>

04-big.png

可以取出來了封救,但是發(fā)現(xiàn)并沒有類方法。在之前捣作,我們曾提及了元類誉结,類對象isa指向就是元類元類是用來存儲類的相關(guān)信息的券躁,所以我們大膽的猜一下:是否類方法存儲在元類的bits中呢惩坑?可以通過lldb命令來驗(yàn)證我們的猜測。下圖是lldb命令的調(diào)試流程

04-類方法.png

通過打印也拜,我們看到了之前的類方法以舒,證明了我們的猜想成立

實(shí)例方法存儲在類的bits屬性中類方法存儲在元類的bits屬性中慢哈。通過bits/元類bits -> methods() -> list獲取實(shí)例方法列表

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔓钟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卵贱,更是在濱河造成了極大的恐慌滥沫,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键俱,死亡現(xiàn)場離奇詭異兰绣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)编振,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門缀辩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人踪央,你說我怎么就攤上這事臀玄。” “怎么了杯瞻?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵镐牺,是天一觀的道長。 經(jīng)常有香客問我魁莉,道長睬涧,這世上最難降的妖魔是什么募胃? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮畦浓,結(jié)果婚禮上痹束,老公的妹妹穿的比我還像新娘。我一直安慰自己讶请,他們只是感情好祷嘶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夺溢,像睡著了一般论巍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上风响,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天嘉汰,我揣著相機(jī)與錄音,去河邊找鬼状勤。 笑死鞋怀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的持搜。 我是一名探鬼主播密似,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼葫盼!你這毒婦竟也來了残腌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贫导,失蹤者是張志新(化名)和其女友劉穎废累,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脱盲,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年日缨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钱反。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匣距,死狀恐怖面哥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毅待,我是刑警寧澤尚卫,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站尸红,受9級特大地震影響吱涉,放射性物質(zhì)發(fā)生泄漏刹泄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一怎爵、第九天 我趴在偏房一處隱蔽的房頂上張望特石。 院中可真熱鬧,春花似錦鳖链、人聲如沸姆蘸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逞敷。三九已至,卻和暖如春灌侣,著一層夾襖步出監(jiān)牢的瞬間推捐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工顶瞳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玖姑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓慨菱,卻偏偏與公主長得像焰络,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子符喝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容