iOS-底層原理8:類 & 類結(jié)構(gòu)分析

類與對(duì)象的關(guān)系

關(guān)于類和對(duì)象的關(guān)系,對(duì)于它的理解可能停留在對(duì)象是類創(chuàng)建(alloc,new)出來(lái)的這種很淺層的關(guān)系上局服,如果要深挖出背后的秘密葛超,還是得從地址和內(nèi)存入手官扣,接下來(lái)就一步步探索。

step1: 創(chuàng)建一個(gè)繼承于NSObjectLBHPerson

//.h  文件
@interface LBHPerson : NSObject

@end

//.m 文件

#import "LBHPerson.h"

@implementation LBHPerson

@end

step2:main.m在導(dǎo)入#import "LBHPerson.h"頭文件芙委,并創(chuàng)建一個(gè)person對(duì)象乞旦,并打上斷點(diǎn)

step3: 通過(guò)lldb輸出person相關(guān)信息

lldb命令在內(nèi)存對(duì)齊 一文中講過(guò)

通過(guò)isa獲取獲取類信息isa與類關(guān)聯(lián)的原理 一文中講過(guò)

0x001d8001000081d1 & 0x00007ffffffffff8ULL

繼續(xù)通過(guò)step3中類的isa獲取類信息得到的是什么呢?

step4: 通過(guò)類的isa獲取對(duì)應(yīng)的類(元類)

我們會(huì)發(fā)現(xiàn)po 0x0000000105d84c70po 0x0000000105d84c48得到的結(jié)果是一樣的题山,都是LBHPerson兰粉,兩個(gè)不同的地址指向同一個(gè)類,0x0000000105d84c48是類0x0000000105d84c70isa通過(guò)獲取類信息得到的顶瞳,我們稱之為元類玖姑。

元類的說(shuō)明

我們知道 對(duì)象的isa 指向,其實(shí)也是一個(gè)對(duì)象慨菱,可以稱為類對(duì)象焰络,其isa的位域指向蘋果定義的元類

  • 元類系統(tǒng)給的,其定義創(chuàng)建都是由編譯器完成符喝,在這個(gè)過(guò)程中闪彼,類的歸屬來(lái)自于元類
  • 元類類對(duì)象,每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類用來(lái)存儲(chǔ) 類方法的相關(guān)信息协饲。
  • 元類本身是沒(méi)有名稱的畏腕,由于與類相關(guān)聯(lián),所以使用了同類名一樣的名稱

step5:繼續(xù)通過(guò)元類isa指向 根元類

元類isa指向根元類NSObject

step6: 根元類isa指向茉稠?

根元類isa指向自己

可以得出一個(gè)關(guān)系鏈:對(duì)象 --> --> 元類 --> NSObject, NSObject 指向自身

一個(gè)類在內(nèi)存中會(huì)不會(huì)存在多份描馅?

LBHPerson為例

通過(guò)對(duì)象personisa獲取到的類信息地址LBHPerson.class獲取到的地址是相同的,這意味一個(gè)類在內(nèi)存中不會(huì)存在多份而线,這個(gè)結(jié)論是否正確呢铭污?我們來(lái)驗(yàn)證一下:

way1: 通過(guò)幾種獲取類對(duì)象的方式
    Class class1 = [LBHPerson class];
    Class class2 = [LBHPerson alloc].class;
    Class class3 = object_getClass([LBHPerson alloc]);
    NSLog(@"\n%p-\n%p-\n%p", class1, class2, class3);

運(yùn)行結(jié)果

三種方式獲取到的類對(duì)象地址相同恋日,即類在內(nèi)存中只有一份

isa走位與繼承關(guān)系圖

isa關(guān)系
  • 示例對(duì)象的isa指針類對(duì)象
  • 類對(duì)象的isa指向元類對(duì)象
  • 元類對(duì)象的isa指向根元類
  • 根元類的isa指向它自己本身,從而形成了閉環(huán)
繼承關(guān)系
  • 類對(duì)象的繼承關(guān)系

    • 繼承于它的父類
    • 父類繼承它的父類
      ...
    • 直到找到根類嘹狞,也就是NSObject
    • NSObject 則繼承于nil岂膳,這也就是所有的根源,即無(wú)中生有
  • 元類的繼承關(guān)系

    • 子類的元類繼承與父類的元類
    • 父類的元類繼承它的父類的元類
      ...
    • 直到找到根元類
    • 根元類則是繼承于NSObject

【注意】實(shí)例對(duì)象之間沒(méi)有繼承關(guān)系磅网,類之間有繼承關(guān)系

舉例說(shuō)明

新建一個(gè)LBHTeacher類繼承與LBHPerson谈截,在main函數(shù)中實(shí)例化

//.h
@interface LBHTeacher : LBHPerson
@end

//.m
#import "LBHTeacher.h"
@implementation LBHTeacher
@end

//main
LBHPerson *person = [LBHPerson alloc];
LBHTeacher *teacher = [LBHTeacher alloc];

它們對(duì)應(yīng)的isa走位和繼承圖

  • isa 走位鏈(兩條)

    • teacher的isa走位鏈:teacher(子類對(duì)象) --> LBHTeacher(子類)-->LBHTeacher(子元類) --> NSObject(根元類) --> NSObject(根元類,即自己)

    • person的isa走位鏈:person(父類對(duì)象) --> LBHPerson(父類)--> LBHPerson(父元類) --> NSObject(根元類) --> NSObject(根元類知市,即自己)

  • superclass繼承鏈(兩條)

    • 類的繼承關(guān)系鏈:LBHTeacher(子類) --> LBHPerson(父類) --> NSObject(根類)--> nil

    • 元類的繼承關(guān)系鏈:LBHTeacher(子元類) --> LBHPerson(父元類) --> NSObject(根元類)--> NSObject(根類)--> nil

對(duì)象的本質(zhì)

對(duì)象的本質(zhì)其實(shí)就是結(jié)構(gòu)體,而編譯到底層會(huì)發(fā)現(xiàn)包含一個(gè)objc_class結(jié)構(gòu)體類型的變量

為什么 對(duì)象都有isa屬性呢速蕊?

在在isa與類關(guān)聯(lián)的原理 一文中使用clang編譯過(guò)main.m文件嫂丙,從編譯后的c++文件中可以看到如下c++源碼:

//LGPerson的底層編譯
struct LBHPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

//NSObject 的底層編譯
struct NSObject_IMPL {
    Class isa;
};

typedef struct objc_class *Class;

在c++底層OC層面的繼承實(shí)際上是子類結(jié)構(gòu)體包含一個(gè)父類結(jié)構(gòu)體作為成員變量

我們對(duì)c++源碼進(jìn)一步進(jìn)行分析

step1: 在objc4源碼中 搜索objc_class

objc_class

通過(guò)源碼可以看出 objc_class 其實(shí)就是繼承自 objc_object

step2: 在objc4源碼中 搜索objc_object

objc_object

objc_classobjc_object 有什么關(guān)系规哲?

  • 結(jié)構(gòu)體類型objc_class 繼承自objc_object類型跟啤,其中objc_object也是一個(gè)結(jié)構(gòu)體,且有一個(gè)isa屬性唉锌,所以objc_class也擁有了isa屬性
  • mian.cpp底層編譯文件中隅肥,NSObject中的isa在底層是由Class 定義的,其中class的底層編碼來(lái)自 objc_class類型袄简,所以NSObject也擁有了isa屬性

objc_object對(duì)象的關(guān)系 (百度面試題)

  • 對(duì)象繼承于objc_object腥放,而objc_class繼承于objc_object,所以對(duì)象繼承與objc_object
總結(jié)
  • 所有的對(duì)象 + + 元類 都有isa屬性
  • 所有的對(duì)象都是由objc_object繼承來(lái)的
  • 簡(jiǎn)單概括就是萬(wàn)物皆對(duì)象绿语,萬(wàn)物皆來(lái)源于objc_object秃症,有以下兩點(diǎn)結(jié)論:
    • 所有以 objc_object為模板 創(chuàng)建的對(duì)象,都有isa屬性
    • 所有以objc_class為模板吕粹,創(chuàng)建的种柑,都有isa屬性
  • 在結(jié)構(gòu)層面可以通俗的理解為上層OC底層的對(duì)接:
    • 下層是通過(guò) 結(jié)構(gòu)體 定義的 模板,例如objc_class匹耕、objc_object
    • 上層是通過(guò)底層的模板創(chuàng)建的 一些類型聚请,例如LBHPerson

objc_classobjc_object稳其、isa驶赏、objectNSObject的關(guān)系如圖所示:

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

探索類結(jié)構(gòu)時(shí)既鞠,我們并不是很清楚里面有些什么母市,但是我們可以通過(guò)得到一個(gè)首地址,然后通過(guò)地址平移去獲取里面所有的损趋。

前面我們已經(jīng)在objc4找到objc_class源碼

struct objc_class : objc_object {
    // Class ISA; //8字節(jié)
    Class superclass; //Class 類型 8字節(jié)
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    //....方法部分省略患久,未貼出
}

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

對(duì)應(yīng)objc_class的結(jié)構(gòu)圖

objc_class
  • isa屬性:繼承自objc_objectisa椅寺,是一個(gè)指針,占 8字節(jié)
  • superclass 屬性:Class類型蒋失,Class是由objc_object定義的返帕,是一個(gè)指針,占8字節(jié)
  • cache屬性:從類型cache_t目前無(wú)法得知篙挽,而cache_t是一個(gè)結(jié)構(gòu)體類型荆萤,結(jié)構(gòu)體的內(nèi)存大小需要根據(jù)內(nèi)部的屬性來(lái)確定,而結(jié)構(gòu)體指針才是8字節(jié)
  • bits屬性:只有首地址經(jīng)過(guò)上面3個(gè)屬性的內(nèi)存大小總和的平移铣卡,才能獲取到bits
計(jì)算cache大小

進(jìn)入cache_t的定義

// 結(jié)構(gòu)體字節(jié)大小链韭,看里面的成員變量,而大部分都是 static 和方法都不存在結(jié)構(gòu)體里面
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;
    mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    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個(gè)字節(jié)
#endif
    uint16_t _occupied; //是uint16_t類型沉迹,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)

    // 方法代碼過(guò)多害驹,自動(dòng)省略
    ...
};

可以得到cache16字節(jié)

bits

bits的位置

objc_class中前三個(gè)屬性的大小為:8+8+16=32鞭呕,所以想獲取bits中的信息可以通過(guò)首地址偏移32字節(jié)獲得。

獲取bits內(nèi)容

先看下bits的結(jié)構(gòu)

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;

     // 代碼過(guò)多宛官,自動(dòng)省略
    ...
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // 代碼過(guò)多琅拌,自動(dòng)省略
    ...
};
class_rw_t
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

    // 省略過(guò)多的代碼  
    ...

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
};

可以看出,我們可以通過(guò)class_rw_t結(jié)構(gòu)體中提供的methods()摘刑、properties()进宝、protocols()獲取到對(duì)應(yīng)的方法、屬性和協(xié)議枷恕。

既然知道可以獲取到對(duì)應(yīng)的方法党晋、屬性和協(xié)議,那就給LBHPerson類添加一些方法和屬性

//.h
{
    NSString *nickName;
}
@property (nonatomic, copy) NSString *name;

- (void)test1;
+ (void)test2;

//.m
- (void)test1
{
    NSLog(@"%s",__func__);
}
+ (void)test2
{
    NSLog(@"%s",__func__);
}

接下來(lái)我們通過(guò)lldb調(diào)試來(lái)查找對(duì)應(yīng)的class_data_bist_t bits徐块,查看相應(yīng)的信息未玻。

獲取bits
  • 其中的data()獲取數(shù)據(jù),是由objc_class提供的方法
data()
獲取屬性列表
  • p $3.properties()命令中的properties方法是由class_rw_t提供的,方法中返回的實(shí)際類型為property_array_t
  • 由于list的類型是property_list_t胡控,是一個(gè)指針扳剿,所以通過(guò) p *$5獲取內(nèi)存中的信息,同時(shí)也證明bits中存儲(chǔ)了 property_list昼激,即屬性列表

在獲取第二個(gè)屬性時(shí)出現(xiàn)報(bào)錯(cuò)庇绽,數(shù)組越界锡搜,我們定義的nickName呢?

這類補(bǔ)充下成員變量瞧掺、實(shí)例變量耕餐、屬性的區(qū)別?
成員變量:在{ }中所聲明的變量都是成員變量(實(shí)例變量是一種特殊的成員變量)
實(shí)例變量:成員變量的一種辟狈,由類聲明的對(duì)象
屬性@property修飾肠缔,編譯器會(huì)自動(dòng)生成setter和getter方法

獲取成員變量

class_rw_t結(jié)構(gòu)體中有個(gè)ro()可以獲取成員變量

通過(guò)lldb調(diào)試來(lái)查找成員變量

class_ro_t結(jié)構(gòu)體中的屬性如下所示,想要獲取ivars哼转,需要ro首地址平移48字節(jié)

struct class_ro_t {
    uint32_t flags;     //4
    uint32_t instanceStart;//4
    uint32_t instanceSize;//4
#ifdef __LP64__
    uint32_t reserved;  //4
#endif

    const uint8_t * ivarLayout; //8
    
    const char * name; //1 ? 8
    method_list_t * baseMethodList; // 8
    protocol_list_t * baseProtocols; // 8
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    
    //方法省略
}

【總結(jié)】

  • 通過(guò)@property定義的屬性明未,也會(huì)存儲(chǔ)在bits屬性中,通過(guò)bits --> data() -->properties() --> list獲取屬性列表壹蔓,其中只包含屬性
  • 通過(guò){}定義的成員變量趟妥,會(huì)存儲(chǔ)在類的bits屬性中,通過(guò)bits --> data() -->ro() --> ivars獲取成員變量列表庶溶,除了包括成員變量煮纵,還包括屬性定義的成員變量

方法列表 methods()

通過(guò)lldb調(diào)試來(lái)查找方法列表

獲取方法列表

通過(guò)打印的count = 4可知懂鸵,存儲(chǔ)了4個(gè)方法,可以通過(guò)p $7.get(i)內(nèi)存偏移的方式獲取單個(gè)方法,i 的范圍是0-3

我們定義的類方法+ (void)test2好像遍歷并沒(méi)有找到偏螺,它存儲(chǔ)在哪呢?

類方法的存儲(chǔ)

methods list中并沒(méi)有找到類方法匆光, 那類方法存儲(chǔ)在哪里套像?下面我們來(lái)分析下:

前面有分析元類元類是用來(lái)存儲(chǔ)的相關(guān)信息的终息,我們大膽猜測(cè)一下:類方法存儲(chǔ)在元類的bits中呢夺巩?通過(guò)lldb命令來(lái)驗(yàn)證我們的猜測(cè):

獲取類方法

根據(jù)打印我們可以知道猜測(cè)是正確的,類方法存儲(chǔ)在元類中周崭。

【總結(jié)】

  • 實(shí)例方法存儲(chǔ)在類的bits屬性中柳譬,通過(guò)bits --> methods() --> list獲取實(shí)例方法列表;
  • 類方法存儲(chǔ)在元類的bits屬性中续镇,通過(guò)元類bits --> methods() --> list獲取類方法列表美澳。

類的結(jié)構(gòu)功能

名稱 類型 功能
isa 指針 指向元類的指針
superclass 指針 指向當(dāng)前類的父類
cache 結(jié)構(gòu)體 用于緩存方法的,用于加速方法的調(diào)用
bits 結(jié)構(gòu)體 存儲(chǔ)類的方法摸航、屬性制跟、協(xié)議等信息的地方
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酱虎,隨后出現(xiàn)的幾起案子雨膨,更是在濱河造成了極大的恐慌,老刑警劉巖读串,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聊记,死亡現(xiàn)場(chǎng)離奇詭異撒妈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)甥雕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門踩身,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人社露,你說(shuō)我怎么就攤上這事挟阻」剑” “怎么了驻民?”我有些...
    開(kāi)封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)文虏。 經(jīng)常有香客問(wèn)我瞒瘸,道長(zhǎng)坷备,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任情臭,我火速辦了婚禮省撑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俯在。我一直安慰自己竟秫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布跷乐。 她就那樣靜靜地躺著肥败,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愕提。 梳的紋絲不亂的頭發(fā)上馒稍,一...
    開(kāi)封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音浅侨,去河邊找鬼纽谒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛如输,可吹牛的內(nèi)容都是我干的鼓黔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼挨决,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼请祖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起脖祈,我...
    開(kāi)封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肆捕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盖高,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體慎陵,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眼虱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了席纽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捏悬。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖润梯,靈堂內(nèi)的尸體忽然破棺而出过牙,到底是詐尸還是另有隱情,我是刑警寧澤纺铭,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布寇钉,位于F島的核電站,受9級(jí)特大地震影響舶赔,放射性物質(zhì)發(fā)生泄漏扫倡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一竟纳、第九天 我趴在偏房一處隱蔽的房頂上張望撵溃。 院中可真熱鬧,春花似錦锥累、人聲如沸缘挑。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卖哎。三九已至鬼悠,卻和暖如春删性,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焕窝。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工蹬挺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人它掂。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓巴帮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親虐秋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子榕茧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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