一、疑惑
在OC程序中,我們知道NSObject是“萬物之源”,所有的類的都繼承自NSObject,我們疑惑的是在OC的底層NSObject是什么樣的?類的結構在OC底層是什么樣的?我們在類中定義的屬性、成員變量、方法、實現的協議等是以什么樣的形式存在的?這篇文章我們將深入OC底層探究NSObject的結構。
二、OC底層的NSObject
1、clang命令獲取main.m的C++代碼
為了知道NSObject底層是什么樣的,clang也許是一個選擇。
Clang是一個由Apple主導編寫,基于LLVM的C/C++/Objective-C編譯器。如果你不知道clang,可以在這里找到你想要的。
在工程目錄中的main.m文件目錄下進入到終端,輸入如下命令
clang -rewrite-objc main.m -o main.cpp
該命令會將main.m編譯成C++的代碼,但是不同平臺支持的代碼肯定是不一樣的。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
如果需要鏈接其他框架,使用-framework參數。比如-framework UIKit
在終端輸入命令以后,會生成一個main.cpp文件。打開main.cpp文件,直接將拉到最下面,我們會看到這樣的一段代碼。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_q5_25qtkmds1pz_0k3brcv05ft00000gn_T_main_218cf4_mi_0,person);
}
return 0;
}
這段代碼便是main函數的底層實現。我們有看到我們熟悉的Person,在這里我們關心的是Person的定義什么樣的。
typedef struct objc_object Person;
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);//獲取當前類
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);//獲取superClass
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);//獲取元類
我們看到Person在底層其實是一個objc_object的結構體,objc_object內部有一個objc_class類型的isa,在前面的詳細了解isa這一篇文章中我們已經分析了isa的走位關系。
- 實例對象的isa指向的是類;
- 類的isa指向的元類;
- 元類指向根元類;
- 根元類指向自己;
- NSObject的父類是nil,根元類的父類是NSObject。
這樣分析起來我們有理由相信NSObject的OC底層實現是objc_object,objc_class是類的OC底層實現,而且兩者之間應該還會存在這謀種關系。
我們再來看下在main.cpp中還能找到些什么熟悉的東西。
//方法
typedef struct objc_method *Method;
//成員變量
typedef struct objc_ivar *Ivar;
//category
typedef struct objc_category *Category;
//屬性列表
typedef struct objc_property *objc_property_t;
2、Objc底層源碼分析
我們從main.cpp找到的東西有限,而且代碼過于龐大,分析起來很繁雜,下面我們從Objc源碼開始分析。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
// objc_class繼承于objc_object,因此
// objc_class中也有isa結構體
struct objc_class : objc_object {
// Class ISA; //8字節
Class superclass;//8字節
// 緩存的是指針和vtable,目的是加速方法的調用 cache占16字節
cache_t cache; // formerly cache pointer and vtable
// class_data_bits_t 相當于是class_rw_t 指針加上rr/alloc標志
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
};
這段代碼是objc_object和objc_class的代碼,我們看到objc_class是從objc_object中繼承而來,
所以objc_class中也有isa結構體。objc_object和objc_class之間的關系如下圖所示。
- isa指向元類的指針,如果你不知道什么是元類,可以看 Classes and Metaclasses這篇文章。
- superclass 當前類的父類。
- cache 用于緩存指針和vtable( formerly cache pointer and vtable)。
bits是什么?這里我們重點探索下bits。
struct class_data_bits_t {
// 相當于 unsigned long bits; 占64位
// bits實際上是一個地址(是一個對象的指針,可以指向class_ro_t,也可以指向class_rw_t)
uintptr_t bits;
};
在objc_class結構體中關于class_data_bits_t的注釋:class_rw_t * plus custom rr/alloc flags,意思是class_data_bits_t相當于class_rw_t * 加上rr/alloc標志。它提供了便捷的方式data()方法返回class_rw_t *指針。
class_rw_t *data() {
// 這里的bits就是class_data_bits_t bits;
return bits.data();
}
class_rw_t* data() {
// FAST_DATA_MASK的值是0x00007ffffffffff8UL
// bits和FAST_DATA_MASK按位與,實際上就是取了bits中的[3,47]位
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
這里將 bits 與 FAST_DATA_MASK 進行位運算,只取其中的 [3, 47] 位轉換成 class_rw_t * 返回。
objc_class中的data()方法調用了class_data_bits_t 結構體中的 data() 方法,返回class_rw_t * 指針。
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
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;
...
};
這段是代碼是class_rw_t結構體的代碼,我們驚奇發現這里有methods(方法)、properties(屬性)、protocols(協議)這些信息,那么我們所需要的類中的方法、屬性、成員變量等信息是不是在這里存儲的呢?下面我來驗證下。
我們定義了一個Person類,在Person類中定義了屬性name,age,成員變量hobby,實例方法sayHello,類方法eat,斷點進入Debug,使用LLVM指令來查看內存信息。
在objc_class的結構中,isa占8字節,superclass占用8字節,cache占用16個字節,將cls的地址偏移32個字節即0x20便是bits的地址。
struct cache_t { struct bucket_t *_buckets;//8字節 mask_t _mask;//4字節 mask_t _occupied;//4字節 }; typedef uint32_t mask_t;
如上截圖所示,在class_rw_t中如愿找到了我們定義的兩個屬性name和age,同樣的也找到了sayHello方法以及name和age的getter/setter方法。但是成員變量hobby和類方法eat在class_rw_t并沒有找到。
我們有注意到在class_rw_t結構中有const class_ro_t *ro這樣一個東西,這是一個常量結構體指針,那么我們猜測成員變量和hobby是不是存放在ro中呢?來看一下class_ro_t的結構。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
果然在class_ro_t中有ivars這樣一個東西,這里存放的就是成員變量,除此之外還有協議列表baseProtocols,屬性列表baseProperties,方法列表baseMethodList。接下來我們利用LLVM來看下ro的內存信息吧。
如上圖所示我們如愿找到了hobby,這就證明了成員變量是存放在class_ro_t中的,但是很遺憾的是類方法eat并沒有找到。那么類方法會不會存在于元類中呢。
上圖是拿到的元類來讀取內存信息,我們可以找到類方法eat,證明了類方法是存在于元類中的。
上面的分析我們得出一下結論
1. 類的對象方法、屬性、實現的協議,成員變量是存在類里面的,其中成員變量是存放在class_ro_t中,class_ro_t也會存放在編譯期間確定的屬性、方法以及遵循的協議。class_rw_t中也會存放類的屬性、方法以及遵循的協議。
2. 類方法在元類中,對象方法在本類中。
通過上面的分析我們已然知道了類屬性、方法、成員變量、遵守的協議等信息的存儲位置,但是我們還不知道這些信息是怎么樣存放進去的。
3、類的信息存儲過程
通過前面的分析我們知道了,objc_class結構中的data()方法可以返回類的信息,那么我們便可以通過setData(class_rw_t *newData)這個方法追本溯源找到了setData的調用這realizeClass方法。
static Class realizeClass(Class cls)
{
runtimeLock.assertLocked();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
// 如果類已經實現了,直接返回
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
// 編譯期間,cls->data指向的是class_ro_t結構體
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// rw結構體已經被初始化(正常不會執行到這里)
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 正常的類都是執行到這里
// Normal class. Allocate writeable class data.
// 初始化class_rw_t結構體
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
// 賦值class_rw_t的class_ro_t,也就是ro
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
// cls->data 指向class_rw_t結構體
cls->setData(rw);
...
};
我們看到其實最開始的時候cls->data是指向class_ro_t的,然后才會把class_ro_t設置到class_rw_t中。那么在realizeClass之前,在class_rw_t中一定拿不到類的相關信息的。
上面在realizeClass里面下的斷點,只需要判斷cls == 當前類的地址就好了,我這里的地址是0x100001490。
如上圖所示我們在realizeClass里面下斷點,這個時候在class_rw_t中并沒有類的相關信息,而在class_ro_t中卻可以找到類的相關信息。那是因為在這之前class_data_bits_t *data 指向的是一個 class_ro_t * 指針。
但是我們前面也分析了class_rw_t結構,是可以拿到類的相關信息的,這是因為執行了methodizeClass方法。methodizeClass方法就是向class_rw_t中添加類的方法列表、協議列表、屬性列表,包括category的方法。
static void methodizeClass(Class cls)
{
...
// Install methods and properties that the class implements itself.
// 將class_ro_t中的methodList添加到class_rw_t結構體中的methodList
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
// 將class_ro_t中的propertyList添加到class_rw_t結構體中的propertyList
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
// 將class_ro_t中的protocolList添加到class_rw_t結構體中的protocolList
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
// 添加category方法
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
...
}
在methodizeClass這個方法里面,將ro中的類的屬性、對象方法,遵守的協議,category方法都添加到了class_rw_t中。這樣就如我們前面所分析的那樣,在class_rw_t結構中可以拿到類的相關信息了。由此就形成以這樣的一個結構。
三、總結
- 在realizeClass方法之前,class_data_bits_t *data 指向的是一個 class_ro_t * 指針。所以在class_rw_t中找不到類的方法、屬性以及協議。
- 在realizeClass方法會通過methodizeClass方法將類的方法、屬性、協議總class_ro_t中添加到class_rw_t中。
- class_rw_t結構體中的ro是一個class_ro_t類型的常量結構體指針,所以在realizeClass方法之后ro中的內容便不可修改,我手動添加的方法也只是修改了class_rw_t中的方法列表中。
- 類的成員變量存儲在class_ro_t結構體中,而不是class_rw_t結構體中。
- 類的類方法在類的元類中,對象方法才在本類中。