為了跳槽碘赖,最近研究了一下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)也是對(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ò)展大多是操作它。
分別說(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
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)部是什么。
通過(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)看源碼:
可以看到,當(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)添加屬性的源碼:
每次增加一個(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)的信息锦担。
我們可以從圖中看到類(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é)果如下圖:
上圖對(duì)myGakki對(duì)象分別添加了3個(gè)變量浆熔,看其內(nèi)存的變化本辐。
結(jié)論:對(duì)象里面存著isa指針和成員變量的內(nèi)容,如果成員變量是對(duì)象医增,則存地址慎皱,如果是基本變量,存的是基本變量的值叶骨。
最后附上一張圖做總結(jié): 圖片來(lái)自網(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