iOS原理探索04--類結(jié)構(gòu)的分析

類的分析

  • 準備工作霜大,我們先創(chuàng)建兩個類繼承NSObjectLGPerson和繼承LGPersonLGStudent
//.h文件
@interface LGPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *lg_name;
- (void)sayHello;
+ (void)sayBye;
@end

//.m文件
@implementation LGPerson
- (void)sayHello
{
}
+ (void)sayBye
{
}
@end

  • main.m文件中如下設(shè)置
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGPerson *person = [LGPerson alloc];
        LGStudent *student = [LGStudent alloc];
        NSLog(@"Hello, World! %@ - %@",person,student);
    }
    return 0;
}

  • 類結(jié)構(gòu)的分析
    mian.m文件LGStudent *student = [LGStudent alloc];處打上斷點運行一下項目工程,使用lldb進行調(diào)試

    lldb調(diào)試過程及輸出結(jié)果

    • 首先我們可以根據(jù)lldb命令得到person的內(nèi)存分布,我們知道0x001d8001000022ddperson的內(nèi)存指針妄迁,接著使用這個指針&0x00007ffffffffff8ULL尿招,可以獲取類的相關(guān)信息$3 = 0x00000001000022d8溜腐。
    • 接著po 0x00000001000022d8來打印類的信息,我們發(fā)現(xiàn)結(jié)果為LGPerson钱豁;
    • x/4gx 0x00000001000022d8,來打印LGPerson的內(nèi)存情況,我們同樣的可以拿到類的isa指針地址0x00000001000022b0;
    • 下一步吉执,p/x 0x00000001000022b0 & 0x00007ffffffffff8ULL,我們來獲取類的信息疯淫,我們得到$5 = 0x00000001000022b0類的指針地址;
    • 下一步:通過po 0x00000001000022b0戳玫,發(fā)現(xiàn)結(jié)果還是LGPerson熙掺,這是為什么呢?這里先簡單說一下這個LGPerson是元類咕宿;下面小節(jié)在詳細說明元類的問題币绩。

二蜡秽、元類,主要有以下幾點說明:

  • 我們都知道 對象的isa是指向缆镣,其實也是一個對象芽突,可以稱為類對象,其isa的位域指向蘋果定義的元類董瞻。

  • 元類系統(tǒng)設(shè)置的诉瓦,其定義和創(chuàng)建都是由編譯器完成,在這個過程中力细,類的歸屬來自于`元類。

  • 元類類對象的類固额,每個類都有一個獨一無二的元類用來存儲 類方法的相關(guān)信息眠蚂。

  • 元類本身是沒有名稱的,由于與類相關(guān)聯(lián)斗躏,所以使用了同類名一樣的名稱逝慧。

總結(jié):由上圖的打印結(jié)果我們可以得出如下結(jié)論:對象 --> 類 --> 元類 --> NSobject, NSObject 指向自身

三啄糙、isa走位 & 繼承關(guān)系

根據(jù)上面的探索以及各種驗證笛臣,對象、類隧饼、元類沈堡、根元類的關(guān)系如下圖所示

isa走位 & 繼承關(guān)系流程圖

isa的走向有以下幾點說明:

  • 實例對象(Instance of Subclass)isa 指向 類(class)
  • 類對象(class) isa指向元類(Meta class)
  • 元類(Meta class)的isa指向根元類(Root metal class)
  • 根元類(Root metal class) 的isa 指向它自己本身,形成閉環(huán)燕雁,這里的根元類就是NSObject诞丽。

superclass(即繼承關(guān)系)的走向也有以下幾點說明:

  • 類(subClass) 繼承自 父類(superClass)
  • 父類(superClass) 繼承自 根類(RootClass),此時的根類是指NSObject拐格。
  • 根類 繼承自 nil僧免,所以根類即NSObject可以理解為萬物起源,即無中生有捏浊。
  • 子類的元類(metal SubClass) 繼承自 父類的元類(metal SuperClass)懂衩。
  • 父類的元類(metal SuperClass) 繼承自根元類(Root metal Class
  • 根元類(Root metal Class) 繼承于根類(Root class)金踪,此時的根類是指NSObject浊洞。

舉例說明

2251862-48a5603729fdf0a9.png

isa 走位鏈

  • studentisa走位鏈:student(子類對象) --> LGStudent (子類)--> LGStudent(子元類) --> NSObject(根元類) --> NSObject(跟根元類,即自己)
    -personisa走位圖:person(父類對象) --> CJLPerson (父類)--> CJLPerson(父元類) --> NSObject(根元類) --> NSObject(跟根元類热康,即自己)

superclass走位鏈

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

四沛申、objc_class & objc_object

在分析objc_class & objc_object我們先引入一個問題,為什么類和對象都有isa屬性呢姐军?
我們先將main.m文件編譯為main.cpp來分析一下這個問題铁材。我們根據(jù)clang編譯的c++源碼可以看出NSObject的底層編譯NSObject_IMPL結(jié)構(gòu)體尖淘,并且對象含有Class isa,代碼如下

struct NSObject_IMPL {
    Class isa;
};

typedef struct objc_class *Class;


struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

下面我們通過在objc4源碼中搜索來探索objc_class & objc_object

  • objc_class在源碼中搜索到兩種相關(guān)源碼
  • 第一種已經(jīng)不再使用了著觉,并且和我們使用Clang編譯后的一樣
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

} OBJC2_UNAVAILABLE;

另外一種村生,我們選擇主要的代碼進行展示如下

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();
    }
};

我們發(fā)現(xiàn)objc_class繼承自objc_object,下面我們再看看objc_object源碼饼丘,

  • objc_object源碼
struct objc_object {
private:
    isa_t isa;
};

總結(jié):

  • 我們根據(jù)源碼發(fā)現(xiàn)objc_object結(jié)構(gòu)體定義了isa作為它的一個屬性趁桃,objc_class繼承自objc_object,所以objc_class也擁有了isa屬性肄鸽。
  • mian.cpp底層編譯文件中卫病,NSObject中的isa在底層是由Class 定義的,其中class的底層編碼來自 objc_class類型典徘,所以NSObject也擁有了isa屬性蟀苛。
  • NSObject是一個類,用它初始化一個實例對象objc逮诲,objc 滿足 objc_object 的特性帜平,所以對象都有一個 isaisa表示指向梅鹦,來自于當(dāng)前的objc_object裆甩。
  • 所以所有的對象都是以 objc_object為模板繼承過來的。
  • 因為對象是 來自NSObject(OC) 齐唆,但是真正到底層的 是一個objc_object(C/C++)的結(jié)構(gòu)體類型,所以 objc_object對象的關(guān)系繼承關(guān)系嗤栓。

objc_classobjc_object箍邮、isa抛腕、objectNSObject等的整體的關(guān)系媒殉,如下圖所示

2251862-7b4c0996f92eb166.png

類的方法

我們根據(jù)下面類的底層實現(xiàn)源碼來探索一下担敌,類的實例方法存儲在哪里,來具體探索一下類的結(jié)構(gòu)是怎樣的廷蓉?

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    

 // ......部分代碼不在展示

}
  • 在探索之前我們需要補充一下知識全封,關(guān)于內(nèi)存偏移,我們先使用實例說明一下這個內(nèi)存偏移桃犬;
    【普通指針】
        //普通指針
        int a = 10; //變量
        int b = 10;
        NSLog(@"%d -- %p", a, &a);
        NSLog(@"%d -- %p", b, &b);

輸出的結(jié)果
截屏2020-09-21 09.37.28.png

我們可以從控制臺看出刹悴,a和b兩個指針指向了同一片的存儲著10的空間。

  • a攒暇、b都指向10土匀,但是a、b的地址``不一樣形用,這是一種拷貝就轧,屬于值拷貝证杭,也稱為淺拷貝

  • a,b的地址之間相差4個字節(jié),這取決于a妒御、b的類型
    【對象指針】

        LGPerson *p1 = [LGPerson alloc]; // p1 是指針
        LGPerson *p2 = [LGPerson alloc];
        NSLog(@"%d -- %p", p1, &p1);
        NSLog(@"%d -- %p", p2, &p2);

輸出結(jié)果

截屏2020-09-21 09.42.12.png

  • p1解愤、p2 是指針,p1是 指向 [LGPerson alloc]創(chuàng)建的空間地址乎莉,即內(nèi)存地址送讲,p2 同理

  • &p1、&p2是 指向 p1惋啃、p2對象指針的地址哼鬓,這個指針 就是 二級指針
    【數(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é)果
截屏2020-09-21 09.45.06.png
  • &c&c[0] 都是取這個數(shù)組的首地址,所以``數(shù)組名等同于首地址边灭;
  • &c&c[1] 相差4個字節(jié)魄宏,地址之間相差的字節(jié)數(shù),主要取決于存儲的數(shù)據(jù)類型可以通過首地址+偏移量取出數(shù)組中的其他元素存筏,其中偏移量數(shù)組的下標內(nèi)存中首地址實際移動的字節(jié)數(shù) 等于偏移量 * 數(shù)據(jù)類型字節(jié)數(shù)`味榛;
計算類結(jié)構(gòu)的內(nèi)存大小

通過上面類結(jié)構(gòu)的源碼我們來計算一下類的大小
-isa屬性:繼承自objc_objectisa椭坚,占 8字節(jié);

  • superclass屬性:Class類型搏色,Class是由objc_object定義的善茎,是一個指針,占8字節(jié)
  • cache屬性:是cache_t結(jié)構(gòu)體類型频轿,我們應(yīng)該按照計算結(jié)構(gòu)體內(nèi)存大小的規(guī)則來計算垂涯,而結(jié)構(gòu)體指針才是8字節(jié);下面代碼可以計算出cache占16字節(jié)
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;
//省略部分代碼
#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é)
//省略部分代碼
}
    
  • bits屬性:只有首地址經(jīng)過上面3個屬性的內(nèi)存大小總和的平移册招,才能獲取到bits`;
類結(jié)構(gòu)中方法分析流程
(lldb) p/x LGPerson.class
(Class) $0 = 0x00000001000022f0 LGPerson
(lldb) //平移32個字節(jié)
error: '//平移32個字節(jié)' is not a valid command.
(lldb) p/x 0x00000001000022f0 + 32
(long) $1 = 0x0000000100002310
(lldb) 獲取bit 
error: '獲取bit' is not a valid command.
(lldb) p (class_data_bits_t *)0x0000000100002310
(class_data_bits_t *) $2 = 0x0000000100002310
(lldb) 通過結(jié)構(gòu)體的data()來獲取bits
error: '通過結(jié)構(gòu)體的data()來獲取bits' is not a valid command.
(lldb) p $2 -> data()
(class_rw_t *) $3 = 0x0000000102018a20
(lldb) 獲取methods()
error: '獲取methods()' is not a valid command.
(lldb) p $3.methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002180
      arrayAndFlag = 4294975872
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
(lldb) 獲取list
error: '獲取list' is not a valid command.
(lldb) p $4.list
(method_list_t *const) $5 = 0x0000000100002180
(lldb) 打印list內(nèi)容
error: '打印list內(nèi)容' is not a valid command.
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100000f85 "v16@0:8"
      imp = 0x0000000100000d80 (KCObjc`-[LGPerson sayHello])
    }
  }
}
(lldb) 
截屏2020-09-18 17.49.44.png
  • 關(guān)于類的屬性
(lldb) p $3.properties()
(const property_array_t) $7 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002230
      arrayAndFlag = 4294976048
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->properties()
(lldb) p $7.list
(property_list_t *const) $8 = 0x0000000100002230
(lldb) p *$8
(property_list_t) $9 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "lg_name", attributes = "T@\"NSString\",C,N,V_lg_name")
  }
}

總結(jié):類的實例方法和類的屬性都存在bits中勒极,我們發(fā)現(xiàn)類的類方法和類的成員變量卻沒有打印是掰,我們可以思考一下,它們存在哪里呢辱匿?類的類方法會不會存在元類里面呢键痛?下一節(jié)我們接著探索一下這個內(nèi)容炫彩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市散休,隨后出現(xiàn)的幾起案子媒楼,更是在濱河造成了極大的恐慌,老刑警劉巖戚丸,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件划址,死亡現(xiàn)場離奇詭異,居然都是意外死亡限府,警方通過查閱死者的電腦和手機夺颤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胁勺,“玉大人世澜,你說我怎么就攤上這事∈鹚耄” “怎么了寥裂?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長案疲。 經(jīng)常有香客問我封恰,道長,這世上最難降的妖魔是什么褐啡? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任诺舔,我火速辦了婚禮,結(jié)果婚禮上备畦,老公的妹妹穿的比我還像新娘低飒。我一直安慰自己,他們只是感情好懂盐,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布褥赊。 她就那樣靜靜地躺著,像睡著了一般莉恼。 火紅的嫁衣襯著肌膚如雪崭倘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天类垫,我揣著相機與錄音司光,去河邊找鬼。 笑死悉患,一個胖子當(dāng)著我的面吹牛残家,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播售躁,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼坞淮,長吁一口氣:“原來是場噩夢啊……” “哼茴晋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起回窘,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诺擅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啡直,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烁涌,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年酒觅,在試婚紗的時候發(fā)現(xiàn)自己被綠了撮执。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡舷丹,死狀恐怖抒钱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颜凯,我是刑警寧澤谋币,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站症概,受9級特大地震影響蕾额,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜穴豫,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逼友。 院中可真熱鬧精肃,春花似錦、人聲如沸帜乞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黎烈。三九已至习柠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間照棋,已是汗流浹背资溃。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烈炭,地道東北人溶锭。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像符隙,于是被迫代替她去往敵國和親趴捅。 傳聞我的和親對象是個殘疾皇子垫毙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354