OC中類(lèi)與對(duì)象在內(nèi)存中的結(jié)構(gòu)

為了跳槽碘赖,最近研究了一下runtime的源碼天通,對(duì)自己進(jìn)行充電。寫(xiě)這篇文章一是為了對(duì)這個(gè)星期研究的總結(jié)(好記性不如爛筆頭),二是為了組內(nèi)的技術(shù)分享(JOJO躯泰!這是我最后的波紋谭跨,收下吧5ぁ)
以下貼的代碼為runtime源碼(objc4-723)碍粥,是通過(guò)c++轉(zhuǎn)化的,暫且可以將其中的結(jié)構(gòu)體看成是類(lèi)骆膝。

類(lèi)

首先祭衩,是類(lèi)的結(jié)構(gòu)。類(lèi)也是一個(gè)對(duì)象阅签,正常的類(lèi)掐暮,在編譯期就確定了類(lèi)的結(jié)構(gòu)和大小。它在內(nèi)存中的地址和大小是不變的政钟,這點(diǎn)很容易證明路克≌两幔可以寫(xiě)個(gè)小demo打印類(lèi)對(duì)象的地址,不改變代碼結(jié)構(gòu)精算,每次運(yùn)行demo瓢宦,取到的地址都是相同的。

接著上源碼:


類(lèi)在源碼中的結(jié)構(gòu)體

類(lèi)也是對(duì)象灰羽,所以繼承objc_object驮履。從中我們可以看出,除去一些方法谦趣,類(lèi)對(duì)象里面實(shí)際存著4塊內(nèi)容疲吸,分別是
ISA:繼承自objc_object,是個(gè)指針前鹅,指向元類(lèi),元類(lèi)中有類(lèi)方法的信息峭梳。
superclass:指向父類(lèi)的指針舰绘。
cache:緩存成員方法,當(dāng)實(shí)例對(duì)象接受到一個(gè)消息時(shí)葱椭,會(huì)優(yōu)先在cache中找捂寿。
bits:class_rw_t的地址和一些flags。其中孵运,類(lèi)中的屬性秦陋,協(xié)議,成員變量治笨,方法等信息都存在class_rw_t中驳概。
先說(shuō)下bits中的標(biāo)簽。在不同系統(tǒng)是不一樣的旷赖。分別是:
32位:

// class is a Swift class
#define FAST_IS_SWIFT         (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR   (1UL<<1)
// data pointer
#define FAST_DATA_MASK        0xfffffffcUL

64位兼容版:

// class is a Swift class
#define FAST_IS_SWIFT           (1UL<<0)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<1)
// class's instances requires raw isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL

64位不兼容版:

// class is a Swift class
#define FAST_IS_SWIFT           (1UL<<0)
// class's instances requires raw isa
#define FAST_REQUIRES_RAW_ISA   (1UL<<1)
// class or superclass has .cxx_destruct implementation
//   This bit is aligned with isa_t->hasCxxDtor to save an instruction.
#define FAST_HAS_CXX_DTOR       (1UL<<2)
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL
// class or superclass has .cxx_construct implementation
#define FAST_HAS_CXX_CTOR       (1UL<<47)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_HAS_DEFAULT_AWZ    (1UL<<48)
// class or superclass has default retain/release/autorelease/retainCount/
//   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
#define FAST_HAS_DEFAULT_RR     (1UL<<49)
// summary bit for fast alloc path: !hasCxxCtor and 
//   !instancesRequireRawIsa and instanceSize fits into shiftedSize
#define FAST_ALLOC              (1UL<<50)
// instance size in units of 16 bytes
//   or 0 if the instance size is too big in this field
//   This field must be LAST
#define FAST_SHIFTED_SIZE_SHIFT 51

class_rw_t

上面的有個(gè)了解就行顺又,源碼注釋也很詳細(xì)。重點(diǎn)來(lái)看class_rw_t中的內(nèi)容等孵,”rw“是readwrite的意思稚照,運(yùn)行時(shí)對(duì)類(lèi)的擴(kuò)展大多是操作它。


class_rw_t源碼中的結(jié)構(gòu)體

分別說(shuō)一下里面的各種字段含義:
flags:存儲(chǔ)了類(lèi)的一些信息俯萌,也是和bits中的flag類(lèi)似果录,通過(guò)二進(jìn)制位置上的0,1判斷咐熙。其位置含義如下:

// Values for class_rw_t->flags
// These are not emitted by the compiler and are never used in class_ro_t. 
// Their presence should be considered in future ABI versions.
// class_t->data is class_rw_t, not class_ro_t
#define RW_REALIZED           (1<<31)
// class is unresolved future class
#define RW_FUTURE             (1<<30)
// class is initialized
#define RW_INITIALIZED        (1<<29)
// class is initializing
#define RW_INITIALIZING       (1<<28)
// class_rw_t->ro is heap copy of class_ro_t
#define RW_COPIED_RO          (1<<27)
// class allocated but not yet registered
#define RW_CONSTRUCTING       (1<<26)
// class allocated and registered
#define RW_CONSTRUCTED        (1<<25)
// available for use; was RW_FINALIZE_ON_MAIN_THREAD
// #define RW_24 (1<<24)
// class +load has been called
#define RW_LOADED             (1<<23)
#if !SUPPORT_NONPOINTER_ISA
// class instances may have associative references
#define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22)
#endif
// class has instance-specific GC layout
#define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21)
// available for use
// #define RW_20       (1<<20)
// class has started realizing but not yet completed it
#define RW_REALIZING          (1<<19)

// NOTE: MORE RW_ FLAGS DEFINED BELOW


// Values for class_rw_t->flags or class_t->bits
// These flags are optimized for retain/release and alloc/dealloc
// 64-bit stores more of them in class_t->bits to reduce pointer indirection.

#if !__LP64__

// class or superclass has .cxx_construct implementation
#define RW_HAS_CXX_CTOR       (1<<18)
// class or superclass has .cxx_destruct implementation
#define RW_HAS_CXX_DTOR       (1<<17)
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define RW_HAS_DEFAULT_AWZ    (1<<16)
// class's instances requires raw isa
#if SUPPORT_NONPOINTER_ISA
#define RW_REQUIRES_RAW_ISA   (1<<15)

version:版本弱恒?在動(dòng)態(tài)創(chuàng)建類(lèi)的方法里面,發(fā)現(xiàn)元類(lèi)賦值為7糖声,正常類(lèi)賦值為0.(不知道干啥)

// Set basic info

    cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    cls->data()->version = 0;
    meta->data()->version = 7;

firstSubclass: 木知(TODO)
nextSiblingClass: 木知(TODO)
methods,properties,protocols分別代表成員方法斤彼,屬性以及協(xié)議分瘦。
ro: 這個(gè)非常重要,它是一個(gè)常量琉苇,并且和class_rw_t很像(坑定有py關(guān)系)嘲玫。其實(shí)ro是readOnly的簡(jiǎn)寫(xiě),對(duì)于已經(jīng)存在的類(lèi),我們無(wú)法更改它的ro屬性.可以進(jìn)去看看ro是什么并扇。

class_ro_t

class_ro_t在源碼中的結(jié)構(gòu)體

flags:同樣有個(gè)flag記錄類(lèi)的一些信息去团。ps.(有個(gè)特殊的值是RO_FUTURE和RW_FUTURE,RO_REALIZED和RW_REALIZED是一樣的穷蛹。其中RW_FUTURE是為了類(lèi)初始化時(shí)將rw強(qiáng)轉(zhuǎn)成ro時(shí)用的標(biāo)簽土陪。)

// Values for class_ro_t->flags
// These are emitted by the compiler and are part of the ABI.
// Note: See CGObjCNonFragileABIMac::BuildClassRoTInitializer in clang
// class is a metaclass
#define RO_META               (1<<0)
// class is a root class
#define RO_ROOT               (1<<1)
// class has .cxx_construct/destruct implementations
#define RO_HAS_CXX_STRUCTORS  (1<<2)
// class has +load implementation
// #define RO_HAS_LOAD_METHOD    (1<<3)
// class has visibility=hidden set
#define RO_HIDDEN             (1<<4)
// class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak
#define RO_EXCEPTION          (1<<5)
// this bit is available for reassignment
// #define RO_REUSE_ME           (1<<6) 
// class compiled with ARC
#define RO_IS_ARC             (1<<7)
// class has .cxx_destruct but no .cxx_construct (with RO_HAS_CXX_STRUCTORS)
#define RO_HAS_CXX_DTOR_ONLY  (1<<8)
// class is not ARC but has ARC-style weak ivar layout 
#define RO_HAS_WEAK_WITHOUT_ARC (1<<9)

// class is in an unloadable bundle - must never be set by compiler
#define RO_FROM_BUNDLE        (1<<29)
// class is unrealized future class - must never be set by compiler
#define RO_FUTURE             (1<<30)
// class is realized - must never be set by compiler
#define RO_REALIZED           (1<<31)

instanceStart:木知啊 TODO
instanceSize: 對(duì)象大小
reserved:我也木知 TODO
ivarLayout:成員變量布局?和weakIvarLayout類(lèi)似肴熏。
name 是類(lèi)名
baseMethodList鬼雀,baseProtocols,baseProperties分別是編譯期類(lèi)的成員方法蛙吏,協(xié)議源哩,屬性的信息,即類(lèi)和類(lèi)擴(kuò)展中的類(lèi)信息鸦做。這些將被賦值回class_rw_t中的類(lèi)信息中励烦。
ivars 是成員變量信息,注意是const泼诱,并且在class_rw_t中并沒(méi)有相關(guān)字段(所以不能動(dòng)態(tài)添加成員變量)坛掠。
weakIvarLayout:
是這東西的地址。治筒。不知道有軟用屉栓,必須是創(chuàng)建中才可以設(shè)置,就是ivarLayout的弱引用
// &UnsetLayout is the default ivar layout during class construction
static const uint8_t UnsetLayout = 0;

對(duì)象

在源碼中矢炼,對(duì)象并沒(méi)有類(lèi)那樣寫(xiě)那么詳細(xì)系瓢,只知道對(duì)象中有一個(gè)isa指針。那么對(duì)象中還有什么呢句灌?我當(dāng)時(shí)有個(gè)猜想:

對(duì)象中存有isa指針夷陋,和成員變量的值。

方法都是存放在對(duì)象的類(lèi)中胰锌,這很容易理解骗绕。因?yàn)槎鄠€(gè)對(duì)象是共用一個(gè)類(lèi)的方法,屬性也是一樣的资昧。但是酬土,關(guān)于成員變量的值,每個(gè)對(duì)象雖然屬性相同格带,但屬性的值是不同的撤缴。每個(gè)類(lèi)的成員變量的值是獨(dú)立的刹枉,那應(yīng)該是存在對(duì)象中。那么下面是我的驗(yàn)證過(guò)程:
方法size_t class_getInstanceSize(Class cls);可以獲取類(lèi)的實(shí)例對(duì)象大小屈呕。我們可以通過(guò)源碼去看它的實(shí)現(xiàn)原理微宝,可以推斷出對(duì)象內(nèi)部是什么。


屏幕快照 2018-03-28 下午10.05.56.png
屏幕快照 2018-03-28 下午10.06.05.png
屏幕快照 2018-03-28 下午10.06.21.png

通過(guò)源碼可以了解到虎眨,調(diào)用獲取對(duì)象大小的方法實(shí)際上是取ro中instanceSize經(jīng)過(guò)對(duì)齊后的值蟋软。那我們?cè)倏纯磇nstanceSize怎么來(lái)的。

前面說(shuō)過(guò)嗽桩,ro是編譯期決定的岳守,我們無(wú)法通過(guò)這份源碼看編譯期的事。但是runtime支持動(dòng)態(tài)創(chuàng)建類(lèi)碌冶,這給了我們窺探的機(jī)會(huì)湿痢。

這里簡(jiǎn)單講一下利用runtime動(dòng)態(tài)添加類(lèi)的過(guò)程。動(dòng)態(tài)創(chuàng)建類(lèi)扑庞,要先調(diào)用Class objc_allocateClassPair方法創(chuàng)建類(lèi)蒙袍,然后可以增加成員變量,增加屬性嫩挤,成員方法等。但如果想用創(chuàng)建的類(lèi)新建實(shí)例對(duì)象消恍,就必須調(diào)用objc_registerClassPair方法進(jìn)行注冊(cè)岂昭。

接下來(lái)看源碼:

objc_initializeClassPair_internal部分源碼內(nèi)容

可以看到,當(dāng)一個(gè)類(lèi)被創(chuàng)建的時(shí)候狠怨,如果沒(méi)有父類(lèi)约啊,那么它的instanceSize值是一個(gè)isa指針大小。如果這時(shí)候再調(diào)用動(dòng)態(tài)注冊(cè)類(lèi)的方法void objc_registerClassPair(Class cls),那用該類(lèi)創(chuàng)建出來(lái)的實(shí)例大小就只是isa指針的大小佣赖。如果有父親恰矩,則它的初始instanceSize值是父類(lèi)的instanceSize大小。
再看動(dòng)態(tài)添加屬性的源碼:


class_addIvar部分源碼

每次增加一個(gè)屬性憎蛤,instanceSize就增加對(duì)應(yīng)屬性的大小外傅。
在源碼中全局搜索了一下setInstanceSize,發(fā)現(xiàn)在動(dòng)態(tài)創(chuàng)建類(lèi)相關(guān)的過(guò)程中俩檬,只有class_addIvar才會(huì)重新設(shè)置instanceSize萎胰。所以,instanceSize就是實(shí)例變量分配空間+isa的地址棚辽。ps:感覺(jué)其中有進(jìn)行對(duì)齊操作技竟,instanceSize不是單純的成員變量的實(shí)際大小,這得之后再研究alignMask字段屈藐。

現(xiàn)在榔组,我們反過(guò)來(lái)看熙尉,為什么成員變量無(wú)法動(dòng)態(tài)添加到已經(jīng)有的類(lèi)中?為什么動(dòng)態(tài)添加成員變量只能在創(chuàng)建類(lèi)和注冊(cè)類(lèi)之間搓扯,這2個(gè)問(wèn)題應(yīng)該都已經(jīng)明白了检痰。

上面我們從源碼角度驗(yàn)證了我的猜想,接著擅编,我們繼續(xù)去demo中驗(yàn)證攀细。
我新建了一個(gè)自定義類(lèi),設(shè)置3個(gè)不同類(lèi)型的屬性。

#import <Foundation/Foundation.h>

@interface Gakki : NSObject

@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)int jin;
@property (nonatomic,strong)NSArray *arr;

@end

在main函數(shù)中創(chuàng)建類(lèi)的實(shí)例對(duì)象爱态。

int main(int argc, const char * argv[]) {

    @autoreleasepool {
        
        Class cla = [Gakki class];
        NSLog(@"%p",cla);

        Gakki *myGakki = [Gakki new];
        NSLog(@"");

        
    }
    return 0;
}

接下來(lái)谭贪,我們運(yùn)用lldb來(lái)打印信息。
先是類(lèi)的信息锦担。


屏幕快照 2018-03-29 下午1.50.59.png

我們可以從圖中看到類(lèi)中ro的內(nèi)容俭识,以及成員變量的結(jié)構(gòu)。
接著洞渔,我們打印實(shí)例對(duì)象在內(nèi)存中的信息套媚。這我們需要x命令。比如x/8xg ,第一個(gè)x代表顯示內(nèi)存,數(shù)字8表示讀取8個(gè)磁椒,第二個(gè)x表示按16進(jìn)制顯示堤瘤,g表示按8字節(jié)讀取。
結(jié)果如下圖:


屏幕快照 2018-03-29 下午2.05.33.png

上圖對(duì)myGakki對(duì)象分別添加了3個(gè)變量浆熔,看其內(nèi)存的變化本辐。

結(jié)論:對(duì)象里面存著isa指針和成員變量的內(nèi)容,如果成員變量是對(duì)象医增,則存地址慎皱,如果是基本變量,存的是基本變量的值叶骨。

最后附上一張圖做總結(jié): 圖片來(lái)自網(wǎng)上


1.jpg

參考:
http://melonteam.com/posts/objectc_dui_xiang_nei_cun_bu_ju_fen_xi/
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/#Runtime-%E7%9A%84%E5%87%BD%E6%95%B0
https://yq.aliyun.com/articles/63323#1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茫多,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子忽刽,更是在濱河造成了極大的恐慌天揖,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缔恳,死亡現(xiàn)場(chǎng)離奇詭異宝剖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歉甚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)万细,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事赖钞⊙兀” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵雪营,是天一觀(guān)的道長(zhǎng)弓千。 經(jīng)常有香客問(wèn)我,道長(zhǎng)献起,這世上最難降的妖魔是什么洋访? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮谴餐,結(jié)果婚禮上姻政,老公的妹妹穿的比我還像新娘。我一直安慰自己岂嗓,他們只是感情好汁展,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著厌殉,像睡著了一般食绿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上公罕,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天器紧,我揣著相機(jī)與錄音,去河邊找鬼楼眷。 笑死品洛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摩桶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼帽揪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼硝清!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起转晰,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芦拿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后查邢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蔗崎,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年扰藕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缓苛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邓深,死狀恐怖未桥,靈堂內(nèi)的尸體忽然破棺而出笔刹,到底是詐尸還是另有隱情,我是刑警寧澤冬耿,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布舌菜,位于F島的核電站,受9級(jí)特大地震影響亦镶,放射性物質(zhì)發(fā)生泄漏日月。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一缤骨、第九天 我趴在偏房一處隱蔽的房頂上張望爱咬。 院中可真熱鬧,春花似錦荷憋、人聲如沸台颠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)串前。三九已至,卻和暖如春实蔽,著一層夾襖步出監(jiān)牢的瞬間荡碾,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工局装, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坛吁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓铐尚,卻偏偏與公主長(zhǎng)得像拨脉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宣增,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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