觸摸iOS底層:類和isa(二)——類、元類、根元類和isa走位圖

你們好,我是葉孤城,和我很是尊敬的大神同名。前一篇文章中,咱們知道了isa的存在、做用,以及類的底層本質是一個objc_object的結構體。可是也帶出了一個新的詞兒—— 元類。今兒個咱們就談談元類,上一篇文章中,那個經典的類結構圖是怎麼畫出來的。markdown

沒錯,就是上面這幅圖。app

元類

解釋元類以前,先說一個咱們熟悉的不能再熟悉的東西: 對象和類。 As we know , 對象是由類產生(準確的說:對象由類對象實例化),咱們前面說過:類也是一個objc_object,叫類對象。那麼 類對象 同理也是由一個 "類" 產生,那麼這個"類"就是 元類spa

當咱們再回顧這條線: obj -> LYPerson(類對象) ->LYPerson(元類) -> NSObject(根元類) -> NSObject(根根元類) 這就理解了爲何有元類,元類是幹什麼的,其實就是產生類對象的類。3d

void lgTestNSObject(){
    // Student實例對象
    Student *object1 = [[Student alloc] init];
    // Student類
    Class class1 = object_getClass(object1);
    // Student元類
    Class metaClass = object_getClass(class1);
    // NSObject 根元類
    Class rootMetaClass = object_getClass(metaClass);
    // NSObject 根根元類
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    NSLog(@"\n%p 實例對象 \n%p 類 %@\n%p 元類 %@\n%p 根元類 %@\n%p 根根元類 %@",object1,class1,NSStringFromClass(class1),metaClass,NSStringFromClass(metaClass),rootMetaClass,NSStringFromClass(rootMetaClass),rootRootMetaClass,NSStringFromClass(rootRootMetaClass));
    
    NSLog(@"=> %p",object_getClass(rootRootMetaClass));
}
複製代碼

image.png

總結:指針

  1. 對象 的類是 類(對象)
  2. 類(對象) 的類是 元類 ;和類同名
  3. 元類 的類是 根元類 NSObject;
  4. 根元類 的類是 本身 ,仍是NSObject。

如下若有說到類對象,其實就是類。調試

類對象

咱們開發中,一個類能夠alloc N個obj,類對象不是,在app的生命週期內,內存中只存在一份。咱們用兩種方式作個實驗:code

第一種:分別打印類對象的地址

void lgTestClassNum(){
    Class class1 = [LYPerson class];
    Class class2 = [LYPerson alloc].class;
    Class class3 = object_getClass([LYPerson alloc]);
    
    NSLog(@"\n[LGPerson class]\t\t\t\t\t%p-\n[LGPerson alloc].class\t\t\t\t%p-\nobject_getClass([LGPerson alloc])\t%p-\n",class1,class2,class3);
}
複製代碼

image.png

第二種:使用LLDB查看

擴展:接下來所會用到的LLDB調試命令

演示的object、class是我代碼裏的斷點處本身定義的orm

  • // 查看變量object的內存地址,併產生一個lldb中的變量$0,代替object
    (lldb) p object // 這是一個對象	
    (Student *) $0 = 0x000000010061eb80
    複製代碼
  • // 也是查看內容
    (lldb) po object	// 這是一個對象
    <Student: 0x10061eb80>
    (lldb) po class // 這是一個Class類型
    複製代碼

Student (lldb) po a // 這是一個int變量a 10對象

* ```
// 轉16進制
(lldb) p/x object // object的內容是地址
(Student *) $2 = 0x000000010061eb80
(lldb) p/x 10 // 10是一個十進制的數
(int) $3 = 0x0000000a
// 轉2進制
(lldb) p/t object
(Student *) $3 = 0b0000000000000000000000000000000100000000011000011110101110000000
複製代碼
  • // x 查看內存
    (lldb) x object
    0x100512ce0: 65 33 00 00 01 80 1d 00 00 00 00 00 00 00 00 00  e3..............
    0x100512cf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    複製代碼
  • // x/[d]gx 查看內存分佈,d表示查看幾塊,一塊是8字節
      
    (lldb) x/gx object
    0x100512ce0: 0x001d800100003365
    (lldb) x/4gx object
    0x100512ce0: 0x001d800100003365 0x0000000000000000 
    0x100512cf0: 0x0000000000000000 0x0000000000000000
    (lldb) x/8gx object
    0x100512ce0: 0x001d800100003365 0x0000000000000000
    0x100512cf0: 0x0000000000000000 0x0000000000000000
    0x100512d00: 0x0000000000000000 0x1000000010050b0e
    0x100512d10: 0x0000000000000000 0x0000000000000000
    複製代碼
  • // 咱們不只能夠查看變量,也能夠直接查看內存地址
    (lldb) po object
    <Student: 0x10046dba0>
    // 根據得到的內存地址,查看內存分佈
    (lldb) x/4gx 0x10046dba0
    0x10046dba0: 0x001d800100003365 0x0000000000000000 
    0x10046dbb0: 0x0000000000000000 0x0000000000000000
    複製代碼

    0x10046dba0就是首地址,內存分佈也是從首地址開始分配繼承

使用lldb驗證上面的class一、class二、class3是同一個類對象

結論:內存地址+內容都是LYPerson,OK,沒有疑問了,類對象只有一個。

好,問題來了

元類在內存中是否是也只有一個?

類對象只有一個,類對象的類元類確定也只有一個。

NSObject在內存中也有一個?

LYPerson 和 LYAnimal 均是繼承NSObject的自定義類,class一、class2分別是他們的元類,class十一、class22則分別是他們的根元類,也就是他們元類的類,都是NSOBject,內存地址也是同樣的,

根元類NSObject的元類的元類的......是什麼樣

這個咱們使用LLDB類查看,OC代碼每次只能運行一次,咱們的LLDB能夠盡情的「追根溯源」

咱們從根元類開始看

一、根元類NSObject 後面的根根元類仍是NSObject ,內存地址同樣;

二、根根元類NSObject 後面的元類仍是本身,內存地址同樣;

因此咱們知道:

  • 根元類NSObject在內存中只有一份;
  • 根元類NSObject的元類——根根元類NSObject天然也是、也只能有一份。

根據目前咱們掌握的知識,咱們作一個isa畫像,對,仍是前一篇文章末尾的那個圖

自定義類的子類isa鏈是否是和自定義類同樣?

前面咱們一直在講直接繼承於NSObject的子類,若是LYPerson又派生了Student類呢?

同理,咱們能夠驗證一番:

準備一個繼承於LYPerson的子類Student

#import "LYPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface Student : LYPerson

@end

NS_ASSUME_NONNULL_END
複製代碼

仍是上面的那個isa指向鏈圖:

咱們分析過runtime源碼:isa是一個Class類型,都是Class是一個objc_class的結構體指針。

typedef struct objc_class *Class;

struct objc_class : objc_object {
    // Class ISA;			// isa是從objc_object繼承而來,因此objc_class與生自帶isa
    Class superclass;	// 重點在這:這個就是標記繼承於哪一個類。Student : LYPerson : NSObject
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
		.
		.
		.
		.
}
複製代碼

因此Class的isa指向的類對象、元類、根元類都是objc_class;因此說,從類對象開始,不光有isa這條線,還有一般所認識的「繼承」着一條主線。類就是個雙主演的電影:isa和superClass。

因此這張圖咱們又變了一下,加上「繼承」這條主線:用實線表示繼承,由於NSOBject在內存中只有一份,因此咱們只保留一個NSObject

這張圖仍是殘缺的,沒有NSObject。NSObject是根類,根類上面沒的繼承了,是nil

而且,NSObject類對象和他的元類不是同一個

NSObject的元類已是根元類!,因此上面的圖,咱們整理一下,去掉標記的person、student,將NSObject元類合併,整理後以下圖所示:

事情並無結束,根元類NSObject是個objc_class,他的superClass指向了哪裏?

根元類NSObject的superClass是什麼?

看完圖確定知道他指向的是根類NSObject,此處僞裝不知道,咱們來找一找。

祭上lldb

咱們經過x/4gx 查看根元類的內存分佈,第8到第16個字節是superclasssuperClass就是NSObject類對象 ,內存地址都是0x0000000100334140

因此,咱們最終得出了這麼一個類的isa和繼承關係的走位圖

問題:爲何lldb 輸出的第二個內存塊0x0000000100334140是superClass?

首先 ,object_class是一個結構體,咱們以前專門談過結構體的內存對齊,咱們知道,結構體的內存分配是按照成員順序,superClass在結構體內的位置排在第二位,第一位是從objc_object繼承的isa,isa佔8個字節,superClass和isa同樣,也佔8個字節,因此,咱們從第二個內存快

struct objc_class : objc_object {
    // Class ISA;		//  佔8個字節,   0~7		
    Class superclass;	// 佔8個字節,	8 ~ 15
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
		.
		.
		.
		.
}
複製代碼

因此,紅色區域是superClass

相關文章
相關標籤/搜索