該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請注明:劉小壯

NSObject
之前的定義
在OC1.0中,Runtime很多定義都寫在NSObject.h文件中,如果之前研究過Runtime的同學(xué)可以應(yīng)該見過下面的定義,定義了一些基礎(chǔ)的信息。
// 聲明Class和id
typedef struct objc_class *Class;
typedef struct objc_object *id;
// 聲明常用變量
typedef struct objc_method *Method;
typedef struct objc_ivar *Ivar;
typedef struct objc_category *Category;
typedef struct objc_property *objc_property_t;
// objc_object和objc_class
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
之前的Runtime結(jié)構(gòu)也比較簡單,都是一些很直接的結(jié)構(gòu)體定義,現(xiàn)在新版的Runtime在操作的時候,各種地址偏移操作和位運算。
之后的定義
后來可能蘋果也不太想讓開發(fā)者知道Runtime內(nèi)部的實現(xiàn),所以就把源碼定義從NSObject中搬到Runtime中了。而且之前的定義也不用了,通過OBJC_TYPES_DEFINED預(yù)編譯指令,將之前的代碼廢棄調(diào)了。
現(xiàn)在NSObject中的定義非常簡單,直接就是一個Class類型的isa變量,其他信息都隱藏起來了。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
這是最新的一些常用Runtime定義,和之前的定義也不太一樣了,用了最新的結(jié)構(gòu)體對象,之前的結(jié)構(gòu)體也都廢棄了。
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct method_t *Method;
typedef struct ivar_t *Ivar;
typedef struct category_t *Category;
typedef struct property_t *objc_property_t;
對象結(jié)構(gòu)體
objc_object定義
在OC中每個對象都是一個結(jié)構(gòu)體,結(jié)構(gòu)體中都包含一個isa的成員變量,其位于成員變量的第一位。isa的成員變量之前都是Class類型的,后來蘋果將其改為isa_t。
struct objc_object {
private:
isa_t isa;
};
OC中的類和元類也是一樣,都是結(jié)構(gòu)體構(gòu)成的。由于類的結(jié)構(gòu)體定義繼承自objc_object,所以其也是一個對象,并且具有對象的isa特征。

所以可以通過isa_t來查找對應(yīng)的類或元類,查找方法應(yīng)該是通過uintptr_t類型的bits,通過按位操作來查找isa_t指向的類的地址。
實例對象或類對象的方法,并不會定義在各個對象中,而是都定義在isa_t指向的類中。查找到對應(yīng)的類后,通過類的class_data_bits_t類型的bits結(jié)構(gòu)體查找方法,對象、類、元類都是同樣的查找原理。
isa_t定義
isa_t是一個union的結(jié)構(gòu)對象,union類似于C++結(jié)構(gòu)體,其內(nèi)部可以定義成員變量和函數(shù)。在isa_t中定義了cls、bits、isa_t三部分,下面的struct結(jié)構(gòu)體就是isa_t的結(jié)構(gòu)體構(gòu)成。
下面對isa_t中的結(jié)構(gòu)體進行了位域聲明,地址從nonpointer起到extra_rc結(jié)束,從低到高進行排列。位域也是對結(jié)構(gòu)體內(nèi)存布局進行了一個聲明,通過下面的結(jié)構(gòu)體成員變量可以直接操作某個地址。位域總共占8字節(jié),所有的位域加在一起正好是64位。
小提示:union中bits可以操作整個內(nèi)存區(qū),而位域只能操作對應(yīng)的位。
下面的代碼是不完整代碼,只保留了arm64部分,其他部分被忽略掉了。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 是32位還是64位
uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關(guān)聯(lián)引用,如果沒有關(guān)聯(lián)引用,可以更快的釋放對象
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的析構(gòu)函數(shù)
uintptr_t shiftcls : 33; // 對象指向類的內(nèi)存地址,也就是isa指向的地址
uintptr_t magic : 6; // 對象是否初始化完成
uintptr_t weakly_referenced : 1; // 對象是否被弱引用或曾經(jīng)被弱引用
uintptr_t deallocating : 1; // 對象是否被釋放中
uintptr_t has_sidetable_rc : 1; // 對象引用計數(shù)太大,是否超出存儲區(qū)域
uintptr_t extra_rc : 19; // 對象引用計數(shù)
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
// ····
# else
// ····
# endif
};
在ARM64架構(gòu)下,isa_t以以下結(jié)構(gòu)進行布局。在不同的CPU架構(gòu)下,布局方式會有所不同,但參數(shù)都是一樣的。

類結(jié)構(gòu)體
objc_class結(jié)構(gòu)體
在Runtime中類也是一個對象,類的結(jié)構(gòu)體objc_class是繼承自objc_object的,具備對象所有的特征。在objc_class中定義了三個成員變量,superclass是一個objc_class類型的指針,指向其父類的objc_class結(jié)構(gòu)體。cache用來處理已調(diào)用方法的緩存。
bits是objc_class的主角,其內(nèi)部只定義了一個uintptr_t類型的bits成員變量,存儲了class_rw_t的地址。bits中還定義了一些基本操作,例如獲取class_rw_t、raw isa狀態(tài)、是否swift等函數(shù)。objc_class結(jié)構(gòu)體中定義的一些函數(shù),其內(nèi)部都是通過bits實現(xiàn)的。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// .....
}
從objc_class的源碼可以看出,可以通過bits結(jié)構(gòu)體的data()函數(shù),獲取class_rw_t指針。我們進入源代碼中看一下,可以看出是通過對uintptr_t類型的bits變量,做位運算查找對應(yīng)的值。
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
uintptr_t本質(zhì)上是一個unsigned long的typedef,unsigned long在64位處理器中占8字節(jié),正好是64位二進制。通過FAST_DATA_MASK轉(zhuǎn)換為二進制后,是取bits中的47-3的位置,正好是取出class_rw_t指針。
在OC中一個指針的長度是47,例如打印一個UIViewController的地址是0x7faf1b580450,轉(zhuǎn)換為二進制是11111111010111100011011010110000000010001010000,最后面三位是占位的,所以在取地址的時候會忽略最后三位。
// 查找第0位,表示是否swift
#define FAST_IS_SWIFT (1UL<<0)
// 當(dāng)前類或父類是否定義了retain、release等方法
#define FAST_HAS_DEFAULT_RR (1UL<<1)
// 類或父類需要初始化isa
#define FAST_REQUIRES_RAW_ISA (1UL<<2)
// 數(shù)據(jù)段的指針
#define FAST_DATA_MASK 0x00007ffffffffff8UL
// 11111111111111111111111111111111111111111111000 總共47位
因為在bits中最后三位是沒用的,所以可以用來存儲一些其他信息。在class_data_bits_t還定義了三個宏,用來對后三位做位運算。
class_ro_t和class_rw_t
和class_data_bits_t相關(guān)的有兩個很重要結(jié)構(gòu)體,class_rw_t和class_ro_t,其中都定義著method list、protocol list、property list等關(guān)鍵信息。
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
在編譯后class_data_bits_t指向的是一個class_ro_t的地址,這個結(jié)構(gòu)體是不可變的(只讀)。在運行時,才會通過realizeClass函數(shù)將bits指向class_rw_t。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
在程序開始運行后會初始化Class,在這個過程中,會把編譯器存儲在bits中的class_ro_t取出,然后創(chuàng)建class_rw_t,并把ro賦值給rw,成為rw的一個成員變量,最后把rw設(shè)置給bits,替代之前bits中存儲的ro。除了這些操作外,還會有一些其他賦值的操作,下面是初始化Class的精簡版代碼。
static Class realizeClass(Class cls)
{
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
ro = (const class_ro_t *)cls->data();
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
isMeta = ro->flags & RO_META;
rw->version = isMeta ? 7 : 0;
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()))
cls->superclass = supercls;
cls->initClassIsa(metacls);
cls->setInstanceSize(ro->instanceSize);
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
methodizeClass(cls);
return cls;
}
在上面的代碼中我們還發(fā)現(xiàn)了兩個函數(shù),addRootClass和addSubclass函數(shù),這兩個函數(shù)的職責(zé)是將某個類的子類串成一個列表,大致是下面的鏈接順序。由此可知,我們是可以通過class_rw_t,獲取到當(dāng)前類的所有子類。
superClass.firstSubclass -> subClass1.nextSiblingClass -> subClass2.nextSiblingClass -> ...
初始化rw和ro之后,rw的method list、protocol list、property list都是空的,需要在下面methodizeClass函數(shù)中進行賦值。函數(shù)中會把ro的list都取出來,然后賦值給rw,如果在運行時動態(tài)修改,也是對rw做的操作。所以ro中存儲的是編譯時就已經(jīng)決定的原數(shù)據(jù),rw才是運行時動態(tài)修改的數(shù)據(jù)。
static void methodizeClass(Class cls)
{
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
假設(shè)創(chuàng)建一個類LXZObject,繼承自NSObject,并為其加入一個testMethod方法,不做其他操作。因為在編譯后objc_class的bits對應(yīng)的是class_ro_t結(jié)構(gòu)體,所以我們打印一下結(jié)構(gòu)體的成員變量,看一下編譯后的class_ro_t是什么樣的。
struct class_ro_t {
flags = 128
instanceStart = 8
instanceSize = 8
reserved = 0
ivarLayout = 0x0000000000000000 <no value available>
name = 0x0000000100000f7a "LXZObject"
baseMethodList = 0x00000001000010c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000 <no value available>
baseProperties = 0x0000000000000000
}
經(jīng)過打印可以看出,一個類的class_ro_t中只會包含當(dāng)前類的信息,不會包含其父類的信息,在LXZObject類中只會包含name和baseMethodList兩個字段,而baseMethodList中只有一個testMethod方法。由此可知,class_rw_t結(jié)構(gòu)體也是一樣的。

初始化過程
下面是已經(jīng)初始化后的isa_t結(jié)構(gòu)體的布局,以及各個結(jié)構(gòu)體成員在結(jié)構(gòu)體中的位置。

union經(jīng)常配合結(jié)構(gòu)體使用,第一次使用union就是對結(jié)構(gòu)體區(qū)域做初始化。在對象初始化時,會對isa_t的bits字段賦值為ISA_MAGIC_VALUE,這就是對union聯(lián)合體初始化的過程。
// 在objc-723中已經(jīng)沒有了
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor)
{
if (!indexed) {
isa.cls = cls;
} else {
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
在對象通過initIsa()函數(shù)初始化時,會通過ISA_MAGIC_VALUE對isa進行初始化。ISA_MAGIC_VALUE是一個16進制的值,將其轉(zhuǎn)換為二進制后,會發(fā)現(xiàn)ISA_MAGIC_VALUE是對nonpointer和magic做初始化。
nonpointer是對之前32位處理器的兼容。在訪問對象所屬的類時,如果是32位則返回之前的isa指針地址,否則表示是64位處理器,則返回isa_t結(jié)構(gòu)體。
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
二進制:11010000000000000000000000000000000000001
補全二進制:23個零+11010000000000000000000000000000000000001
隨后會通過位域,對has_cxx_dtor和shiftcls做初始化,這時候就已經(jīng)有四個字段被初始化了。has_cxx_dtor表示是否有C++或OC的析構(gòu)方法,在打印方法列表時,經(jīng)常能看到一個名為.cxx_destruct的方法,就和這個字段有關(guān)系。
在計算機中為了對存儲區(qū)(Memory or Disk)讀取方便,所以在寫入和讀取時,會對內(nèi)存有對其操作。一般是以字節(jié)為單位進行對其,這樣也是對讀寫速度的優(yōu)化。在對shiftcls進行賦值時,對Class的指針進行了位移操作,向右位移三位。這是因為類指針為了內(nèi)存對其,將最后三位用0填充,所以這三位是沒有意義的。
isa結(jié)構(gòu)體
0000000001011101100000000000000100000000001110101110000011111001
0x5d8001003ae0f8
類對象地址
100000000001110101110000011111000
0x1003ae0f8
將類對象地址右移三位為100000000001110101110000011111,正好符合isa_t地址中shiftcls的部分,前面不足補零。
外界獲取Class時,應(yīng)該通過ISA()函數(shù),而不是像之前一樣直接訪問isa指針。在ISA()函數(shù)中,是對isa_t的結(jié)構(gòu)體做與運算,是通過ISA_MASK宏進行的,轉(zhuǎn)換為二進制的話,正好是把shiftcls的地址取出來。
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
#define ISA_MASK 0x0000000ffffffff8ULL
111111111111111111111111111111111000
Tagged Pointer
從iPhone5s開始,iOS設(shè)備開始引入了64位處理器,之前的處理器一直都是32位的。
但是在64位處理器中,指針長度以及一些變量所占內(nèi)存都發(fā)生了改變,32位一個指針占用4字節(jié),但64位一個指針占用8字節(jié);32位一個long占用4字節(jié),64位一個long占用8字節(jié)等,所以在64位上內(nèi)存占用會多出很多。
蘋果為了優(yōu)化這個問題,推出了Tagged Pointer新特性。之前一個指針指向一個地址,而Tagged Pointer中一個指針就代表一個值,以NSNumber為例。
NSNumber *number1 = @1;
NSNumber *number2 = @3;
NSNumber *number3 = @54;
// 輸出
(lldb) p number1
(__NSCFNumber *) $3 = 0xb000000000000012 (int)1
(lldb) p number2
(__NSCFNumber *) $4 = 0xb000000000000032 (int)3
(lldb) p number3
(__NSCFNumber *) $5 = 0xb000000000000362 (int)54
通過上面代碼可以看出,使用了Tagged Pointer新特性后,指針中就存儲著對象的值。例如一個值為1的NSNumber,指針就是0xb000000000000012,如果拋去前面的0xb和后面的2,中間正好就是16進制的值。
蘋果通過Tagged Pointer的特性,明顯的提升了執(zhí)行效率并節(jié)省了很多內(nèi)存。在64位處理器下,內(nèi)存占用減少了將近一半,執(zhí)行效率也大大提升。由于通過指針來直接表示數(shù)值,所以沒有了malloc和free的過程,對象的創(chuàng)建和銷毀速度提升幾十倍。
isa_t
對于對象指針也是一樣,在OC1.0時代isa是一個真的指針,指向一個堆區(qū)的地址。而OC2.0時代,一個指針長度是八字節(jié)也就是64位,在64位中直接存儲著對象的信息。當(dāng)查找對象所屬的類時,直接在isa指針中進行位運算即可,而且由于是在棧區(qū)進行操作,查找速度是非??斓?。
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
例如isa_t本質(zhì)上是一個結(jié)構(gòu)體,如果創(chuàng)建結(jié)構(gòu)體再用指針指向這個結(jié)構(gòu)體,內(nèi)存占用是很大的。但是Tagged Pointer特性中,直接把結(jié)構(gòu)體的值都存儲到指針中,這就相當(dāng)節(jié)省內(nèi)存了。
蘋果不允許直接訪問isa指針,和Tagged Pointer也是有關(guān)系的。因為在Tagged Pointer的情況下,isa并不是一個指針指向另一塊內(nèi)存區(qū),而是直接表示對象的值,所以通過直接訪問isa獲取到的信息是錯誤的。
簡書由于排版的問題,閱讀體驗并不好,布局、圖片顯示、代碼等很多問題。所以建議到我Github上,下載Runtime PDF合集。把所有Runtime文章總計九篇,都寫在這個PDF中,而且左側(cè)有目錄,方便閱讀。

下載地址:Runtime PDF
麻煩各位大佬點個贊,謝謝!??