OC底層原理--類結(jié)構(gòu)分析

通過上一篇文章對isa的分析,我們知道了所有的對象都包含isa,并且isa存儲了類的相關(guān)信息,所以這篇文章我們主要通過isa來引出類的底層結(jié)構(gòu)以及一些信息

代碼分析

創(chuàng)建對象

LYPerson *person = [[LYPerson alloc] init];

lldb分析

(lldb) x/4gx person
0x100513970: 0x001d80010000820d 0x0000000000000000
0x100513980: 0x0000000000000000 0x0000000000000000
(lldb) 

通過之前的分析,我們知道所有對象的第一個屬性都是isa,故上面的0x001d80010000820disa的地址,我們繼續(xù)通過打印isa的地址來看下

(lldb) po 0x001d80010000820d & 0x00007ffffffffff8ULL
LYPerson
(lldb) p/x 0x001d80010000820d & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008208
(lldb) po 0x0000000100008208
LYPerson

這里需要& 0x00007ffffffffff8ULL主要是因為isa只有中間的44(或33)位才是類信息,所以需要把其它位置為0,通過上面的地址打印我們得出結(jié)論:對象的isa指向類信息,那么我們繼續(xù)來打印下類的isa看看是什么

(lldb) x/4gx 0x0000000100008208
0x100008208: 0x00000001000081e0 0x00007fff98b0e118
0x100008218: 0x0000000100513990 0x0001802400000003
(lldb) p/x 0x00000001000081e0 & 0x00007ffffffffff8ULL
(unsigned long long) $5 = 0x00000001000081e0
(lldb) po 0x00000001000081e0
LYPerson

這里我們發(fā)現(xiàn)類的isa只想的信息,打印出來也是LYPerson,通過內(nèi)存地址我們可以肯定此處的LYPerson跟上一步的LYPerson不是同一個,但是類在內(nèi)存中又只會存在一份,因此我們引出了元類的概念,至此,我們得出結(jié)論:類的isa指向元類信息.
接下來我們給出類在內(nèi)存中只存在一份的證明以及元類的說明

//MARK:--- 分析類對象內(nèi)存 存在個數(shù)
void testClassNum(){
    Class class1 = [LYPerson class];
    Class class2 = [LYPerson alloc].class;
    Class class3 = object_getClass([LYPerson alloc]);
    NSLog(@"\n%p-\n%p-\n%p-\n%p", class1, class2, class3);
}
image.png
  • 元類是系統(tǒng)給的,其定義和創(chuàng)建都是由編譯器完成,在這個過程中,類的歸屬來自于元類(類似于對象的歸屬是類)

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

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

接下來我們繼續(xù)分析下元類的isa

(lldb) x/4gx 0x00000001000081e0
0x1000081e0: 0x00007fff98b0e0f0 0x00007fff98b0e0f0
0x1000081f0: 0x00000001006471f0 0x0003e03500000007
(lldb) p/x 0x00007fff98b0e0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00007fff98b0e0f0
(lldb) po 0x00007fff98b0e0f0
NSObject

通過打印isa我們發(fā)現(xiàn)LYPerson的元類的isa指向了NSObject,那么此處的NSObject是不是就是我們的根類呢?我們來證明下

(lldb) p/x NSObject.class
(Class) $9 = 0x00007fff98b0e118 NSObject

很明顯,兩個NSObject并不是同一個類,因此我們認定上面的NSObject根元類,這里我們得出結(jié)論:元類的isa指向根元類.接下來我們繼續(xù)打印根元類的isa

(lldb) x/4gx 0x00007fff98b0e0f0
0x7fff98b0e0f0: 0x00007fff98b0e0f0 0x00007fff98b0e118
0x7fff98b0e100: 0x00000001020081b0 0x0005e03100000007
(lldb) p/x 0x00007fff98b0e0f0 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x00007fff98b0e0f0
(lldb) po 0x00007fff98b0e0f0
NSObject

這里我們發(fā)現(xiàn)根元類的isa與它本身的地址相同,由此我們得出結(jié)論:根元類的isa指向它本身

isa & 繼承鏈

通過上面的分析再加上我們的繼承關(guān)系,于是就有了下面這個著名的圖


image.png

上面這幅圖,對于isa的走位我們已經(jīng)通過代碼證明過了,沒有任何問題.那么關(guān)于集成鏈有兩點需要注意下

  • 根元類的父類為根類
  • 根類的父類為nil

以上兩點也說明了NSObject做為基類,是萬物起源,我們可以看下底層編譯代碼

struct NSObject_IMPL {
    Class isa;
};

// 結(jié)構(gòu)體
typedef struct objc_class *Class;

Class分析

源碼分析

通過上面的分析我們來到了我們非常熟悉的Class,接下來我們就來具體分析下Class的定義objc_class的源碼實現(xiàn)

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

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
    //....方法部分省略嚣崭,未貼出
}

通過源碼我們可以發(fā)現(xiàn)objc_class中包含的元素主要有ISA,superclass,cache,bits,對與ISA我們已經(jīng)了解了,superclass很明顯是父類,也沒什么好分析的,那么剩下的cachebits,cache主要是緩存數(shù)據(jù),我們可以放到后面,這里主要還是分析下bits里面有哪些內(nèi)容,那么如何獲取到bits呢,這里我們補充下個知識點--內(nèi)存偏移

內(nèi)存偏移

偏移地址是指段內(nèi)相對于段起始地址的偏移值,
例如一個存儲器的大小是1KB,可以把它分為4段蕉陋,第一段的地址范圍就是0—255阎姥,第二段的地址范圍就是256-511记舆,依次類推。

我們拿數(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);
0x7ffeefbff560 -- 0x7ffeefbff560 - 0x7ffeefbff564
0x7ffeefbff560 -- 0x7ffeefbff564 - 0x7ffeefbff568

通過上面我們可以得出:數(shù)組的首地址即位第一個元素的地址,第二個元素的地址即為第一個元素的地址加上元素類型所占的字節(jié)大小,int占用4個字節(jié),即+4,若為double+8

綜上,內(nèi)存偏移就可以理解為首地址加偏移量

class_data_bits_t bits分析

在了解了內(nèi)存偏移后,我們來嘗試下獲取bits,首先ISAsuperclass都是Class類型,Class是結(jié)構(gòu)體指針類型,占用8個字節(jié),所以加一起是16個字節(jié),接下來我們分析下cache,源碼如下

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; //是指針伊磺,占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個字節(jié)
#endif
    uint16_t _occupied; //是uint16_t類型,uint16_t是 unsigned short 的別名摘能,占 2個字節(jié)

通過源碼,我們得出cache占用16個字節(jié),因此bits距首地址偏移32個字節(jié),接下來我們通過lldb進行調(diào)試

// 打印LYPerson的內(nèi)存地址
(lldb) x/4gx LYPerson.class
0x100008200: 0x00000001000081d8 0x000000010034c140
0x100008210: 0x000000010184dd90 0x0001802400000003
// 首地址為0x100008200,偏移32位獲取class_data_bits_t
(lldb) p (class_data_bits_t *)0x100008220
(class_data_bits_t *) $30 = 0x0000000100008220
(lldb) 

通過objc_class源碼我們可以發(fā)現(xiàn)bits中存儲的信息為class_rw_t結(jié)構(gòu)體類型,可以通過data()方法獲取.源碼如下

struct objc_class : objc_object {
    ...
    class_rw_t *data() const {
        return bits.data();
    }
    ...
}

我們繼續(xù)通過lldb調(diào)試

(lldb) p $30->data()
(class_rw_t *) $31 = 0x0000000100645c60
(lldb) p *$31
(class_rw_t) $32 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000216
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

接下來通過查看class_rw_t的源碼,我們發(fā)現(xiàn),class_rw_t存儲了屬性列表,方法列表,協(xié)議,ro(class_ro_t類型)等信息,如下圖

image.png

接下來,繼續(xù)通過lldb調(diào)試

// 獲取屬性列表
(lldb) p $32.properties()
(const property_array_t) $33 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000081a8
      arrayAndFlag = 4295000488
    }
  }
}
// 獲取屬性數(shù)組
(lldb) p $33.list
(property_list_t *const) $34 = 0x00000001000081a8
// 打印具體值
(lldb) p *$34
(property_list_t) $35 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}

(lldb) p $32.methods()
(const method_array_t) $36 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000080e0
      arrayAndFlag = 4295000288
    }
  }
}
(lldb) p $36.list
(method_list_t *const) $37 = 0x00000001000080e0
(lldb) p *$37
(method_list_t) $38 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 5
    first = {
      name = "sayName"
      types = 0x0000000100003f78 "v16@0:8"
      imp = 0x0000000100003d60 (KCObjc`-[LYPerson sayName])
    }
  }
}
(lldb) p $38.get(0)
(method_t) $39 = {
  name = "sayName"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003d60 (KCObjc`-[LYPerson sayName])
}
(lldb) p $38.get(1)
(method_t) $40 = {
  name = "saySex"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003d90 (KCObjc`-[LYPerson saySex])
}
(lldb) p $38.get(2)
(method_t) $41 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003e10 (KCObjc`-[LYPerson .cxx_destruct])
}
(lldb) p $38.get(3)
(method_t) $42 = {
  name = "name"
  types = 0x0000000100003f8c "@16@0:8"
  imp = 0x0000000100003dc0 (KCObjc`-[LYPerson name])
}
(lldb) p $38.get(4)
(method_t) $43 = {
  name = "setName:"
  types = 0x0000000100003f94 "v24@0:8@16"
  imp = 0x0000000100003de0 (KCObjc`-[LYPerson setName:])
}
(lldb) p $38.get(5)
Assertion failed: (i < count), function get, file /Users/LY/Desktop/LY/objc4_debug/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 

通過上面獲取到的properties我們得到了類的所有屬性列表,但是卻并沒有找到對應(yīng)的成員變量的列表,于是我們通過查看上面的ro的源碼可以發(fā)現(xiàn)有一個ivars屬性,同樣的方式分析ro我們找到了所有的成員變量,這里不做贅述.大家可以自行嘗試

通過上面獲取到的methods我們得到了類的所有實例方法列表,但是卻并沒有找到對應(yīng)的類方法的列表,根據(jù)我們最開始探索的isa指向,我們知道對象的isa指向類,類的isa指向元類.那么現(xiàn)在對象方法存在了類中,會不會類方法存在元類中呢,于是我們通過上面的步驟對元類進行調(diào)試,發(fā)現(xiàn)類方法確實存在元類中,這里不做贅述.大家可以自行嘗試

通過上面的調(diào)試我們得出如下結(jié)論

  • 類中存儲了類的所有屬性列表,``方法列表,協(xié)議等信息在class_rw_t`中
  • 類的方法列表中除了實例方法還包括settergetter方法
  • 類方法并不存在類信息中而是存在元類中
  • 成員變量存在class_ro_tivars

至此,對于類的結(jié)構(gòu)以及類中的class_data_bits_t存儲了哪些信息我們已經(jīng)基本都了解了,這篇文章就先到這里了,后續(xù)我們再對類的cache進行分析.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末续崖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子团搞,更是在濱河造成了極大的恐慌严望,老刑警劉巖逻恐,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拨匆,死亡現(xiàn)場離奇詭異,居然都是意外死亡挽拂,警方通過查閱死者的電腦和手機惭每,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門台腥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黎侈,“玉大人峻汉,你說我怎么就攤上這事≈肼担” “怎么了蔚携?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵酝蜒,是天一觀的道長亡脑。 經(jīng)常有香客問我邀跃,道長拍屑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮蒜茴,結(jié)果婚禮上移必,老公的妹妹穿的比我還像新娘崔泵。我一直安慰自己憎瘸,他們只是感情好幌甘,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布锅风。 她就那樣靜靜地躺著皱埠,像睡著了一般边器。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恒界,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音婆誓,去河邊找鬼洋幻。 笑死翅娶,一個胖子當(dāng)著我的面吹牛燥翅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杨名,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吁断!你這毒婦竟也來了掷伙?” 一聲冷哼從身側(cè)響起炎咖,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寒波,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體升熊,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡俄烁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年页屠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辰企。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖潜索,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情整陌,我是刑警寧澤随夸,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布逃魄,位于F島的核電站,受9級特大地震影響澜搅,放射性物質(zhì)發(fā)生泄漏伍俘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一癌瘾、第九天 我趴在偏房一處隱蔽的房頂上張望妨退。 院中可真熱鬧,春花似錦蜕企、人聲如沸咬荷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽幸乒。三九已至,卻和暖如春唇牧,著一層夾襖步出監(jiān)牢的瞬間罕扎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工丐重, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腔召,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓扮惦,卻偏偏與公主長得像臀蛛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崖蜜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359