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

一 對(duì)象 isa 類 元類

上代碼

//MARK: - 分析類在內(nèi)存存在個(gè)數(shù)
void lgTestClassNum(){
    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é)果:
     0x100003270-
     0x100003270-
     0x100003270-
     0x100003270
     */
}

上述代碼中,通過(guò)三種不同方式創(chuàng)建LGPerson三個(gè)類對(duì)象,但三個(gè)類對(duì)象的地址一模一樣,說(shuō)明類在內(nèi)存中只有一個(gè).

那我們帶著上面的結(jié)論來(lái)看下面這組lldb打印結(jié)果

lldb調(diào)試結(jié)果

問(wèn)題 : 為什么Person的地址有兩個(gè)

  • 首先我們獲取了person對(duì)象的isa地址 進(jìn)行 p/x 0x001d8001000081d1 & 0x00007ffffffffff8ULL 操作 獲得 person對(duì)象的類地址0x00000001000081d0 跟我們進(jìn)行p/x person.class 的結(jié)果相同
  • 0x00000001000081a8Person類的isa指向即為Person類的元類

元類的說(shuō)明

  • 下面來(lái)解釋什么是元類栋操,主要有以下幾點(diǎn)說(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),所以使用了同類名一樣的名稱

如果我們繼續(xù)進(jìn)行isa執(zhí)行溯源 最終可得出以下結(jié)論 這里就不截圖了有興趣的同學(xué)可以自己驗(yàn)證下

  • isa指向 : 對(duì)象 --> 類 --> 元類 --> NSobject(根元類) -->NSObject 指向自身
  • 繼承關(guān)系: 子類 --> 父類 --> 根類(NSobject) --> nil
  • 元類繼承關(guān)系 : 子元類 --> 父元類 --> 根元類(NSobject) --> NSobject --> nil
著名的 isa走位 & 繼承關(guān)系 圖
isa走位 & 繼承關(guān)系 圖

代碼驗(yàn)證 :

// NSObject實(shí)例對(duì)象
    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í)例對(duì)象\n%p 類\n%p 元類\n%p 根元類\n%p 根根元類",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    /*
        打印結(jié)果:
        0x1007160b0 實(shí)例對(duì)象
        0x7fff98847118 類
        0x7fff988470f0 元類
        0x7fff988470f0 根元類
        0x7fff988470f0 根根元類
        */

二 類的結(jié)構(gòu)

我們新建一個(gè) macOS 控制臺(tái)項(xiàng)目主穗,然后新建兩個(gè)類 Teacher 和 Student 出來(lái)泻拦,其中 Teacher 繼承自 NSObject, Student 繼承自 Teacher。

#import <Foundation/Foundation.h>
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *stu = [Student alloc];
        
        NSLog(@"stu: %@", stu);
    }
    return 0;
}

使用clang命令

clang -rewrite-objc main.m -o main.cpp

擴(kuò)展

  • 如果是目標(biāo)文件導(dǎo)入了UIKit 框架忽媒,則我們需要使用clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk main.m , 其中 13.7 要根據(jù)當(dāng)前的模擬器SDK版本來(lái)做更改聪轿。

  • 其中xcode 安裝的時(shí)候順帶安裝了 xcrun 命令,xcrun 命令在 clang 的基礎(chǔ)上進(jìn)行了一些封裝猾浦,要更簡(jiǎn)單好用一些陆错。
    xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模擬器)
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp(手機(jī))

這個(gè)命令是將我們的 main.m 文件編譯成C++文件main.cpp,我們打開(kāi)這個(gè)文件搜索 Student,我們發(fā)現(xiàn)有多個(gè)地方都出現(xiàn)了 Student金赦,然后我們使用全局搜索關(guān)鍵字typedef struct objc_object音瓷,最終我們找到了class的定義

typedef struct objc_class *Class;

然后去 源碼中直接搜索 struct objc_class刨裆,然后定位到 objc-runtime-new.h 文件

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();
    }
    
    // 省略部分代碼.......
}

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

可以看到objc_class是繼承于objc_object的传轰,而objc_class本身是沒(méi)有isa的,其isa繼承自objc_object,對(duì)象又是由類創(chuàng)建
由此可知objc_object 與 對(duì)象的關(guān)系彩匕,對(duì)象漠烧、類杏愤、元類都有isa,那么可以說(shuō)所有的對(duì)象都是按照objc_object的模板繼承而來(lái)的,所有的對(duì)象都繼承于objc_object.

三 類結(jié)構(gòu)探索

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
    
    //....方法部分省略已脓,未貼出
}
  • isa屬性:繼承自objc_objectisa珊楼,占 8字節(jié)

  • superclass 屬性:Class類型,Class是由objc_object定義的度液,是一個(gè)指針厕宗,占8字節(jié)

  • cache屬性 : 結(jié)構(gòu)體類型 大小要看內(nèi)部結(jié)構(gòu)

  • bits屬性:只有首地址經(jīng)過(guò)上面3個(gè)屬性的內(nèi)存大小總和的平移画舌,才能獲取到bits

cache大小分析

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 是一個(gè)結(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個(gè)字節(jié)
#endif
    uint16_t _occupied; //是uint16_t類型旭咽,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)

總結(jié):所以最后計(jì)算出cache類的內(nèi)存大小 = 12 + 2 + 2 = 16字節(jié)

bits

通過(guò)類的首地址平移32字節(jié)獲取 class_data_bits_t*:

(lldb) p/x Person.class
(Class) $0 = 0x00000001000082d0 Person
//首地址平移32字節(jié)
(lldb) p (class_data_bits_t*) 0x1000082f0
(class_data_bits_t *) $1 = 0x00000001000082f0
(lldb) p $1-> data()
//data方法獲取bits
(class_rw_t *) $2 = 0x000000010072e1d0

現(xiàn)在我們獲取到 class_data_bits_t 結(jié)構(gòu)體中的class_rw_t 繼續(xù)探索源碼發(fā)現(xiàn)結(jié)構(gòu)體中有提供相應(yīng)的方法去獲取 屬性列表把将、方法列表等轻专,如下所示

源碼

通過(guò)class_rw_t提供的方法忆矛,繼續(xù)探索bits中的屬性列表

(lldb) p $2.properties()
(const property_array_t) $3 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008238
      }
      arrayAndFlag = 4295000632
    }
  }
}
  Fix-it applied, fixed expression was: 
    $2->properties()
(lldb) p $3.list
(const RawPtr<property_list_t>) $4 = {
  ptr = 0x0000000100008238
}
(lldb)  p $4.ptr
(property_list_t *const) $5 = 0x0000000100008238
(lldb) p *$5
(property_list_t) $6 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 3)
}
(lldb) p $6.get(0)
(property_t) $7 = (name = "fafd", attributes = "T@\"NSString\",C,N,V_fafd")
(lldb) p $6.get(1)
(property_t) $8 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $6.get(2)
(property_t) $9 = (name = "sex", attributes = "T@\"NSString\",C,N,V_sex")
(lldb) p $6.get(3)
Assertion failed: (i < count), function get, file /Users/caomengfei/Downloads/objc4-debugTest-master/objc4-818.2/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 

-+
OK 現(xiàn)在我們的三個(gè)屬性已經(jīng)都找到了 繼續(xù)嘗試下 方法列表 分為類方法實(shí)例方法,與屬性一樣,這次試用
methods 獲取

方法列表

現(xiàn)在我們打印出共 8個(gè)方法 3個(gè)屬性的set get方法 我們添加的一個(gè)setPersonName實(shí)例方法
問(wèn)題 成員方法 與類方法存儲(chǔ)在何處

觀察源碼發(fā)現(xiàn)class_rw_tclass_ro_t這個(gè)屬性察蹲,通過(guò)查看其定義,發(fā)現(xiàn)其中有一個(gè)ivars屬性催训,我們可以做如下猜測(cè):是否成員變量就存儲(chǔ)在這個(gè)ivar_list_t類型的ivars屬性中呢洽议?

    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }
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;
    
    //方法省略
}

lldb調(diào)試實(shí)例變量存儲(chǔ)

上圖可知ivars屬性,其中的count為4 ,除了age 還有_name ,_sex等成員變量

  • 通過(guò){}定義的成員變量漫拭,會(huì)存儲(chǔ)在類的bits屬性中亚兄,通過(guò)bits --> data() -->ro() --> ivars獲取成員變量列表,除了包括成員變量采驻,還包括屬性定義的成員變量

  • 通過(guò)@property定義的屬性审胚,也會(huì)存儲(chǔ)在bits屬性中,通過(guò)bits --> data() --> properties() --> lis獲取屬性列表礼旅,其中只包含屬性

類方法列表(類方法存儲(chǔ)在元類中,從元類中獲取)有興趣的小伙伴可以驗(yàn)證下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膳叨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子痘系,更是在濱河造成了極大的恐慌菲嘴,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汰翠,死亡現(xiàn)場(chǎng)離奇詭異龄坪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)复唤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門健田,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人佛纫,你說(shuō)我怎么就攤上這事抄课〕牵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵跟磨,是天一觀的道長(zhǎng)间聊。 經(jīng)常有香客問(wèn)我,道長(zhǎng)抵拘,這世上最難降的妖魔是什么哎榴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮僵蛛,結(jié)果婚禮上尚蝌,老公的妹妹穿的比我還像新娘。我一直安慰自己充尉,他們只是感情好飘言,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著驼侠,像睡著了一般姿鸿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒源,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天苛预,我揣著相機(jī)與錄音,去河邊找鬼笋熬。 笑死热某,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胳螟。 我是一名探鬼主播昔馋,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼糖耸!你這毒婦竟也來(lái)了秘遏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蔬捷,失蹤者是張志新(化名)和其女友劉穎垄提,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體周拐,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铡俐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妥粟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片审丘。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖勾给,靈堂內(nèi)的尸體忽然破棺而出滩报,到底是詐尸還是另有隱情锅知,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布脓钾,位于F島的核電站售睹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏可训。R本人自食惡果不足惜昌妹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望握截。 院中可真熱鬧飞崖,春花似錦、人聲如沸谨胞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胯努。三九已至牢裳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間康聂,已是汗流浹背贰健。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工胞四, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恬汁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓辜伟,卻偏偏與公主長(zhǎng)得像氓侧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子导狡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

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