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

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

首先,是類的結(jié)構(gòu)。類也是一個對象,正常的類,在編譯期就確定了類的結(jié)構(gòu)和大小。它在內(nèi)存中的地址和大小是不變的,這點很容易證明。可以寫個小demo打印類對象的地址,不改變代碼結(jié)構(gòu),每次運(yùn)行demo,取到的地址都是相同的。

接著上源碼:


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

類也是對象,所以繼承objc_object。從中我們可以看出,除去一些方法,類對象里面實際存著4塊內(nèi)容,分別是
ISA:繼承自objc_object,是個指針,指向元類,元類中有類方法的信息。
superclass:指向父類的指針。
cache:緩存成員方法,當(dāng)實例對象接受到一個消息時,會優(yōu)先在cache中找。
bits:class_rw_t的地址和一些flags。其中,類中的屬性,協(xié)議,成員變量,方法等信息都存在class_rw_t中。
先說下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

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


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

分別說一下里面的各種字段含義:
flags:存儲了類的一些信息,也是和bits中的flag類似,通過二進(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:版本?在動態(tài)創(chuàng)建類的方法里面,發(fā)現(xiàn)元類賦值為7,正常類賦值為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: 這個非常重要,它是一個常量,并且和class_rw_t很像(坑定有py關(guān)系)。其實ro是readOnly的簡寫,對于已經(jīng)存在的類,我們無法更改它的ro屬性.可以進(jìn)去看看ro是什么。

class_ro_t

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

flags:同樣有個flag記錄類的一些信息。ps.(有個特殊的值是RO_FUTURE和RW_FUTURE,RO_REALIZED和RW_REALIZED是一樣的。其中RW_FUTURE是為了類初始化時將rw強(qiáng)轉(zhuǎn)成ro時用的標(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: 對象大小
reserved:我也木知 TODO
ivarLayout:成員變量布局?和weakIvarLayout類似。
name 是類名
baseMethodList,baseProtocols,baseProperties分別是編譯期類的成員方法,協(xié)議,屬性的信息,即類和類擴(kuò)展中的類信息。這些將被賦值回class_rw_t中的類信息中。
ivars 是成員變量信息,注意是const,并且在class_rw_t中并沒有相關(guān)字段(所以不能動態(tài)添加成員變量)。
weakIvarLayout:
是這東西的地址。。不知道有軟用,必須是創(chuàng)建中才可以設(shè)置,就是ivarLayout的弱引用
// &UnsetLayout is the default ivar layout during class construction
static const uint8_t UnsetLayout = 0;

對象

在源碼中,對象并沒有類那樣寫那么詳細(xì),只知道對象中有一個isa指針。那么對象中還有什么呢?我當(dāng)時有個猜想:

對象中存有isa指針,和成員變量的值。

方法都是存放在對象的類中,這很容易理解。因為多個對象是共用一個類的方法,屬性也是一樣的。但是,關(guān)于成員變量的值,每個對象雖然屬性相同,但屬性的值是不同的。每個類的成員變量的值是獨立的,那應(yīng)該是存在對象中。那么下面是我的驗證過程:
方法size_t class_getInstanceSize(Class cls);可以獲取類的實例對象大小。我們可以通過源碼去看它的實現(xiàn)原理,可以推斷出對象內(nèi)部是什么。


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

通過源碼可以了解到,調(diào)用獲取對象大小的方法實際上是取ro中instanceSize經(jīng)過對齊后的值。那我們再看看instanceSize怎么來的。

前面說過,ro是編譯期決定的,我們無法通過這份源碼看編譯期的事。但是runtime支持動態(tài)創(chuàng)建類,這給了我們窺探的機(jī)會。

這里簡單講一下利用runtime動態(tài)添加類的過程。動態(tài)創(chuàng)建類,要先調(diào)用Class objc_allocateClassPair方法創(chuàng)建類,然后可以增加成員變量,增加屬性,成員方法等。但如果想用創(chuàng)建的類新建實例對象,就必須調(diào)用objc_registerClassPair方法進(jìn)行注冊。

接下來看源碼:

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

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


class_addIvar部分源碼

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

現(xiàn)在,我們反過來看,為什么成員變量無法動態(tài)添加到已經(jīng)有的類中?為什么動態(tài)添加成員變量只能在創(chuàng)建類和注冊類之間,這2個問題應(yīng)該都已經(jīng)明白了。

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

#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)建類的實例對象。

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

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

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

        
    }
    return 0;
}

接下來,我們運(yùn)用lldb來打印信息。
先是類的信息。


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

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


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

上圖對myGakki對象分別添加了3個變量,看其內(nèi)存的變化。

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

最后附上一張圖做總結(jié): 圖片來自網(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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容