為了跳槽,最近研究了一下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,取到的地址都是相同的。
接著上源碼:

類也是對象,所以繼承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ò)展大多是操作它。

分別說一下里面的各種字段含義:
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

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)部是什么。



通過源碼可以了解到,調(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)行注冊。
接下來看源碼:

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

每次增加一個屬性,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來打印信息。
先是類的信息。

我們可以從圖中看到類中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é)果如下圖:

上圖對myGakki對象分別添加了3個變量,看其內(nèi)存的變化。
結(jié)論:對象里面存著isa指針和成員變量的內(nèi)容,如果成員變量是對象,則存地址,如果是基本變量,存的是基本變量的值。
最后附上一張圖做總結(jié): 圖片來自網(wǎng)上

參考:
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