Runtime原始碼解讀2(類和物件)
2019-10-10
在Runtime原始碼解讀(實現面向物件初探)中,從Cocoa框架中的runtime.h
標頭檔案公開的資料結構及 API 對 runtime 整體有一個大概的認知。從本文開始具體分析 Apple 開源的runtime原始碼。本文介紹 runtime 如何通過C語言結構體實現類和物件,該部分應該是 runtime 的最核心程式碼。
注意:Github 搜尋到的有一千多顆星的RetVal/objc-runtime工程,版本是750,最新公開的程式碼版本是756,後者在 ARC 支援、
ivarLayout
定義、Swift 相容等方面有變動。
一、類的定義
Objective-C 中類的本質是objc_class
-
isa
:objc_class
繼承objc_object
結構體,因此也包含isa
指標,主要功能是指向物件的型別,新版本 runtime 中,isa
指標並不一定是Class
型別而是包含64 bit 資料的點陣圖(bitmap),在 4.1 中詳細介紹; -
superclass
:指向父類的指標,用於組織類的繼承鏈; -
cache
:類使用雜湊表資料結構快取最近呼叫方法,以提高方法查詢效率(TODO:後續獨立文章中會介紹); -
bits
:class_data_bits_t
結構體型別,該結構體主要用於記錄,儲存類的資料的class_rw_t
結構體的記憶體地址。通過date()
bits
的有效位域指向的記憶體空間,返回class_rw_t
結構體;setData(class_rw_t *newData)
用於設定bits
的值;
注意:上述 bitmap 並不是圖片的點陣圖,而是指資料被視為簡單的二進位制數,將其中的一些或所有 bit 賦予特殊的含義,共同表示一種含義的 bit 或 bit 的集合稱為位域。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data () {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// 類的方法在文章第三部分詳細介紹
...
};
複製程式碼
二、類的資料
類的資料主要儲存在class_data_bits_t
結構體中,其成員僅有一個bits
指標。objc_class
的data()
方法用於獲取bits
成員的 4~47 位域(FAST_DATA_MASK
)中儲存的class_rw_t
結構體地址。類的資料儲存在class_rw_t
結構體中,剩餘的部分儲存在ro
指標指向的class_ro_t
結構體中。
class_rw_t
、class_ro_t
結構體名中,rw
是 read write 的縮寫,ro
是 read only 的縮寫,可見class_ro_t
的儲存類的只讀資訊,這些資訊在類完成註冊後不可改變。以類的成員變數列表為例(成員變數列表儲存在class_ro_t
結構體中)。若應用類註冊到記憶體後,使用類構建了若干例項,此時若新增成員變數必然需要對記憶體中的這些類重新分配記憶體,這個操作的花銷是相當大的。若考慮再極端一些,為根類NSObject
新增成員變數,則記憶體中基本所有 Objective-C 物件都需要重新分配記憶體,如此龐大的計算量在執行時是不可接受的。
#if !__LP64__
#define FAST_DATA_MASK 0xfffffffcUL
#elif 1
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#endif
#if (!__LP64__ || TARGET_OS_WIN32 || \
(TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC))
# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif
struct class_data_bits_t {
uintptr_t bits;
private:
bool getBit(uintptr_t bit)
{
return bits & bit;
}
...
public:
// 獲取類的資料
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// 設定類的資料
void setData(class_rw_t *newData)
{
// 僅在類註冊、構建階段才允許呼叫setData
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
...
// 是否支援非指標型別isa,在4.1介紹物件的isa指標時詳細介紹
#if FAST_REQUIRES_RAW_ISA
bool instancesRequireRawIsa() {
return getBit(FAST_REQUIRES_RAW_ISA);
}
void setInstancesRequireRawIsa() {
setBits(FAST_REQUIRES_RAW_ISA);
}
#elif SUPPORT_NONPOINTER_ISA
// 主流機型一般走到這個編譯分支
bool instancesRequireRawIsa() {
return data()->flags & RW_REQUIRES_RAW_ISA;
}
void setInstancesRequireRawIsa() {
data()->setFlags(RW_REQUIRES_RAW_ISA);
}
#else
bool instancesRequireRawIsa() {
return true;
}
void setInstancesRequireRawIsa() {
// nothing
}
#endif
...
};
複製程式碼
1.1 class_rw_t 結構體
類的主要資料儲存在bits
中,bits
以點陣圖儲存class_rw_t
結構體,用於記錄類的關鍵資料,如成員變數列表、方法列表、屬性列表、協議列表等等,class_rw_t
僅包含三個基本的位操作方法。class_rw_t
包含以下成員:
-
flags
:32位點陣圖,標記類的狀態; -
version
:標記類的型別,0
表示類為非元類,7
表示類為元類; -
ro
:儲存類的只讀資料,註冊類後ro
中的資料標記為只讀,成員變數列表儲存在ro
中; -
methods
:方法列表,其型別method_array_t
為二維陣列容器(TODO:後續在獨立文章介紹); -
properties
:屬性列表,其型別property_array_t
為二維陣列容器(TODO:後續在獨立文章介紹); -
protocols
:協議列表,其型別protocol_array_t
為二維陣列容器; -
firstSubclass
:類的首個子類,與nextSiblingClass
記錄所有類的繼承鏈組織成的繼承樹; -
nextSiblingClass
:類的下一個兄弟類; -
demangledName
:類名,來自Swift的類會包含一些特別字首,demangledName
是處理後的類名; -
index
:標記類的物件的isa
是否為index
型別;
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
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;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
//設定set指定的位
void setFlags(uint32_t set)
{
OSAtomicOr32Barrier(set,&flags);
}
// 清空clear指定的位
void clearFlags(uint32_t clear)
{
OSAtomicXor32Barrier(clear,&flags);
}
// 設定set指定的位,清空clear指定的位
void changeFlags(uint32_t set,uint32_t clear)
{
assert((set & clear) == 0);
uint32_t oldf,newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf,newf,(volatile int32_t *)&flags));
}
};
複製程式碼
class_rw_t
的flags
成員中比較重要的一些位域定義列舉如下,均以RW_
為字首,這些位域在類註冊後仍可讀寫。
/************* 類註冊後可讀寫的flags位域 *************/
// 類是已經註冊的類
#define RW_REALIZED (1<<31)
// 類是尚未解析的future class
#define RW_FUTURE (1<<30)
// 類是已經初始化的類
#define RW_INITIALIZED (1<<29)
// 類是正在初始化的類
#define RW_INITIALIZING (1<<28)
// class_rw_t->ro是class_ro_t的堆拷貝
// 此時類的class_rw_t->ro是可寫入的,拷貝之前ro的記憶體區域鎖死不可寫入
#define RW_COPIED_RO (1<<27)
// 類是正在構建而仍未註冊的類
#define RW_CONSTRUCTING (1<<26)
// 類是已經構建完成並註冊的類
#define RW_CONSTRUCTED (1<<25)
// 類是load方法已經呼叫過的類
#define RW_LOADED (1<<23)
#if !SUPPORT_NONPOINTER_ISA
// 類是可能例項可能存在關聯物件的類
// 預設編譯選項下,無需定義該位,因為都可能有關聯物件
#define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22)
#endif
// 類是具有例項相關的GC layout的類
#define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21)
// 類是禁止使用關聯物件的類
#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20)
// 類是正在註冊,但是未註冊完成的類
#define RW_REALIZING (1<<19)
複製程式碼
1.1 class_ro_t 結構體
類完成註冊後,類的例項佔用的記憶體大小、成員變數列表、成員變數記憶體佈局等重要資訊需要固定下來,這些在類註冊後需要標記為只讀的資料儲存在class_ro_t
結構體中,class_rw_t
結構體的ro
成員為指向該結構體的指標。class_ro_t
結構體包含以下主要成員:
-
flags
:32位點陣圖,標記類的狀態。需要注意class_ro_t
的flags
使用的位域和前面介紹的class_rw_t
的flags
使用的位域是完全不同的; -
instanceStart
:類的成員變數,在例項的記憶體空間中的起始偏移量; -
instanceSize
:類的例項佔用的記憶體空間大小; -
ivarLayout
:成員變數記憶體佈局,標記例項佔用的記憶體空間中哪些WORD儲存了成員變數資料; -
name
:類名; -
baseMethodList
:基礎方法列表,在類定義時指定的方法列表; -
baseProtocols
:協議列表; -
ivars
:成員變數列表; -
weakIvarLayout
:weak成員變數佈局; -
baseProperties
:基礎屬性列表,在類定義時指定的屬性列表;
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 *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this,size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this,size);
return ro;
}
}
};
複製程式碼
class_ro_t
的flags
成員中比較重要的一些位域定義列舉如下,均以RO_
為字首,這些位域在類註冊後標記為只讀。
/************* 類註冊後只讀的flags位域 *************/
// 類是元類
#define RO_META (1<<0)
// 類是根類
#define RO_ROOT (1<<1)
// 類有CXX構造/解構函式
#define RO_HAS_CXX_STRUCTORS (1<<2)
// 類有實現load方法
// #define RO_HAS_LOAD_METHOD (1<<3)
// 隱藏類
#define RO_HIDDEN (1<<4)
// class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak
#define RO_EXCEPTION (1<<5)
// class has ro field for Swift metadata initializer callback
#define RO_HAS_SWIFT_INITIALIZER (1<<6)
// 類使用ARC選項編譯
#define RO_IS_ARC (1<<7)
// 類有CXX解構函式,但沒有CXX建構函式
#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)
// 類禁止使用關聯物件
#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10)
// 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)
複製程式碼
注意:實際上在類的構建階段,有時會操作
class_rw_t
去置flags
中一些RO_
字首的位域,但僅發生在重疊的29/30/31位域。
三、類的行為
本章介紹objc_class
結構體中定義的方法。
3.1 類載入過程相關行為
呼叫 runtime API 動態建立類的過程,包括三個步驟:
- 呼叫
Class objc_allocateClassPair(...)
構建類; - 新增必要的成員變數、方法等元素;
- 呼叫
void objc_registerClassPair(Class cls)
註冊類;
然而,runtime 從映象(image)載入類的過程會更加精細,在載入類的不同階段會被標記為不同的型別(還是objc_class
結構體,只是flags
不同),例如:future class(懶載入類)、remapped class(已重對映類)、realized class(已認識類)、allocated class(已分配記憶體類)、named class(已確定名稱類)、loaded class(已載入類)、initialized class(已初始化類)等。接下來重點介紹 future class、remapped class 和 realized class。其中標記為 allocated class 和 named class 只是簡單地將類新增到全域性管理的雜湊表中,因此僅穿插在 future class、remapped class 中介紹;loaded class、initialized class 分別為已執行load
方法的類和已執行initialize()
方法的類。
objc_class
結構體中與類的載入過程相關的函式程式碼如下,基本在class_rw_t
、class_ro_t
的flags
中存在RW_
、RO_
字首的位域與之對應:
// 查詢是否正在初始化(initializing)
bool isInitializing() {
return getMeta()->data()->flags & RW_INITIALIZING;
}
// 標記為正在初始化(initializing)
void setInitializing() {
assert(!isMetaClass());
ISA()->setInfo(RW_INITIALIZING);
}
// 是否已完成初始化(initializing)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
void setInitialized(){
Class metacls;
Class cls;
assert(!isMetaClass());
cls = (Class)this;
metacls = cls->ISA();
// 關於alloc/dealloc/Retain/Release等特殊方法的判斷及處理
...
metacls->changeInfo(RW_INITIALIZED,RW_INITIALIZING);
}
bool isLoadable() {
assert(isRealized());
return true; // any class registered for +load is definitely loadable
}
// 獲取load方法的IMP
IMP
objc_class::getLoadMethod()
{
runtimeLock.assertLocked();
const method_list_t *mlist;
assert(isRealized());
assert(ISA()->isRealized());
assert(!isMetaClass());
assert(ISA()->isMetaClass());
// 在類的基礎方法列表中查詢load方法的IMP
mlist = ISA()->data()->ro->baseMethods();
if (mlist) {
for (const auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
if (0 == strcmp(name,"load")) {
return meth.imp;
}
}
}
return nil;
}
// runtime是否已認識類
bool isRealized() {
return data()->flags & RW_REALIZED;
}
// 是否future class
bool isFuture() {
return data()->flags & RW_FUTURE;
}
複製程式碼
3.1.1 future class
objc_class
的isFuture()
函式,用於判斷類是否為 future class。本節通過程式碼一步步探討 future class 的概念,future class 對理解類的載入過程有重要作用。
3.1.1.1 future class 生成
首先看 future class 是如何生成的。addFutureNamedClass(const char *name,Class cls)
函式用於將傳入的cls
引數,配置為類名為name
的 future class,包含以下操作:
- 分配
cls
所需的class_rw_t
、class_ro_t
的記憶體空間; - 將
cls
的類名置為name
; - 將
class_rw_t
的RO_FUTURE
位置為1,RO_FUTURE
等於RW_FUTURE
; - 以
name
為關鍵字,將cls
新增到一個全域性的雜湊表futureNamedClasses
;
static NXMapTable *future_named_class_map = nil;
static NXMapTable *futureNamedClasses()
{
runtimeLock.assertLocked();
if (future_named_class_map) return future_named_class_map;
// future_named_class_map is big enough for CF’s classes and a few others
future_named_class_map =
NXCreateMapTable(NXStrValueMapPrototype,32);
return future_named_class_map;
}
static void addFutureNamedClass(const char *name,Class cls)
{
void *old;
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t),1);
class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t),1);
ro->name = strdupIfMutable(name);
rw->ro = ro;
cls->setData(rw);
cls->data()->flags = RO_FUTURE;
old = NXMapKeyCopyingInsert(futureNamedClasses(),name,cls);
assert(!old);
}
複製程式碼
追蹤呼叫addFutureClass(...)
的程式碼,最終追溯到Class objc_getFutureClass(const char *name)
,該函式並沒有在 runtime 原始碼中被呼叫到。而用於從namedFutureClasses
雜湊表中獲取 future class 的popFutureClass(...)
函式是有間接通過readClass(...)
函式被廣泛呼叫。因此,構建 future class 的邏輯大多隱藏在 runtime 的內部實現中未公佈,只有使用 future class 的邏輯是開源的。
Class objc_getFutureClass(const char *name)
{
Class cls;
cls = look_up_class(name,YES,NO);
if (cls) {
if (PrintFuture) {
_objc_inform("FUTURE: found %p already in use for %s",(void*)cls,name);
}
return cls;
}
// 若查詢不到名為name的類則構建future class
return _objc_allocateFutureClass(name);
}
Class _objc_allocateFutureClass(const char *name)
{
mutex_locker_t lock(runtimeLock);
Class cls;
NXMapTable *map = futureNamedClasses();
if ((cls = (Class)NXMapGet(map,name))) {
// 存在名為name的 future class
return cls;
}
// 分配用於儲存objc_class的記憶體空間
cls = _calloc_class(sizeof(objc_class));
// 構建名為name的future class並全域性記錄到 futureNamedClasses 雜湊表
addFutureNamedClass(name,cls);
return cls;
}
複製程式碼
3.1.1.2 future class 應用
addFutureClass(...)
操作明顯是全域性記錄 future class 的過程,接下來追溯 何時用到 future class。static Class popFutureNamedClass(const char *name)
用於從futureNamedClasses
雜湊表中彈出類名為name
的 future class,這是獲取全域性記錄的 future class 的唯一入口。
static Class popFutureNamedClass(const char *name)
{
runtimeLock.assertLocked();
Class cls = nil;
if (future_named_class_map) {
cls = (Class)NXMapKeyFreeingRemove(future_named_class_map,name);
if (cls && NXCountMapTable(future_named_class_map) == 0) {
NXFreeMapTable(future_named_class_map);
future_named_class_map = nil;
}
}
return cls;
}
複製程式碼
popFutureNamedClass
在Class readClass(Class cls,bool headerIsBundle,bool headerIsPreoptimized)
函式中有被呼叫到,後者用於讀取cls
中的類資料,關鍵處理邏輯表述如下:
- 若
futureNamedClasses
雜湊表中存在cls->mangledName()
類名的 future class,則將cls
重對映(remapping)到新的類newCls
(具體重對映過程在 3.1.2 中詳細討論),然後將newCls
標記為 remapped class,以cls
為關鍵字新增到全域性記錄的remappedClasses()
雜湊表中; - 將
cls
標記為 named class,以cls->mangledName()
類名為關鍵字新增到全域性記錄的gdb_objc_realized_classes
雜湊表中,表示 runtime 開始可以通過類名查詢類(注意元類不需要新增); - 將
cls
及其元類標記為 allocated class,並將兩者均新增到全域性記錄的allocatedClasses
雜湊表中(無需關鍵字),表示已為類分配固定記憶體空間;
注意:傳入
readClass(...)
的cls
引數是Class
型別,而函式返回結果也是Class
,為什麼讀取類資訊是“從類中讀取類資訊”這樣怪異的過程呢?其實是因為cls
引數來源於 runtime 未開源的 從映象(image)中讀取類的過程,該過程輸出的objc_class
存在特殊之處:要麼輸出 future class,要麼輸出普通類但是其bits
指向的是class_ro_t
結構體而非class_rw_t
,之所以如此是因為從映象讀取的是編譯時決議的靜態資料,本來就應該儲存在class_ro_t
結構體中。
Class readClass(Class cls,bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
//類的繼承鏈上,存在既不是根類(RO_ROOT位為0)又沒有超類的類,則為missingWeakSuperclass
//注意:這是唯一的向remappedClasses中新增nil值的入口
if (missingWeakSuperclass(cls)) {
addRemappedClass(cls,nil);
cls->superclass = nil;
return nil;
}
// 相容舊版本libobjc的配置,可忽略
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (Class newCls = popFutureNamedClass(mangledName)) {
// 已經全域性記錄該類名的 future class
// 構建newCls並將cls的內容拷貝到其中,儲存future class的rw中的資料
// 以cls為關鍵字將構建的newCls新增到全域性記錄的remappedClasses雜湊表中
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro;
memcpy(newCls,cls,sizeof(objc_class));
rw->ro = (class_ro_t *)newCls->data();
newCls->setData(rw);
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
addRemappedClass(cls,newCls);
replacing = cls;
cls = newCls;
}
if (headerIsPreoptimized && !replacing) {
// 已存在該類名的named class
assert(getClassExceptSomeSwift(mangledName));
} else {
// 將類新增到 named classes
addNamedClass(cls,mangledName,replacing);
// 將類新增到 allocated classes
addClassTableEntry(cls);
}
// 設定RO_FROM_BUNDLE位
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
複製程式碼
從上文readClass(...)
程式碼if (Class newCls = popFutureNamedClass(mangledName))
分支內free((void *)old_ro)
語句,得出在cls
對映到newCls
過程中,完全丟棄了 future class 的ro
資料。最後,結合以上所有程式碼,可以歸納以下結論:
- Future class 類的有效資料實際上僅有:類名和
rw
。rw
中的資料作用也非常少,僅使用flags
的RO_FUTURE
(實際上就是RW_FUTURE
)標記類是 future class; - Future class 的作用是為指定類名的類,提前分配好記憶體空間,呼叫
readClass(...)
函式讀取類時,才正式寫入類的資料。 Future class 是用於支援類的懶載入機制;
3.1.2 remapped class
在上文 3.1.1 有提到類的重對映,重對映的類被標記為 remapped class,並以對映前的類為關鍵字,新增到全域性的remappedClass
雜湊表中。回顧Class readClass(Class cls,bool headerIsPreoptimized)
函式中,類的重對映程式碼如下,關於處理過程的詳細描述已註釋到程式碼中:
// 1. 若該類名已被標記為future class,則彈出該類名對應的future class 賦值給newCls
if (Class newCls = popFutureNamedClass(mangledName)) {
// 2. rw記錄future class的rw
class_rw_t *rw = newCls->data();
// 3. future class的ro記為old_ro,後面釋放其佔用的記憶體空間並丟棄
const class_ro_t *old_ro = rw->ro;
// 4. 將cls中的資料拷貝到newCls,主要是要沿用cls的isa、superclass和cache資料
memcpy(newCls,sizeof(objc_class));
// 5. rw記錄cls的ro
rw->ro = (class_ro_t *)newCls->data();
// 6. 沿用future class的rw、cls的ro
newCls->setData(rw);
// 7. 釋放future class的ro佔用的空間
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
// 8. 將newCls以cls為關鍵字新增到remappedClasses雜湊表中
addRemappedClass(cls,newCls);
replacing = cls;
cls = newCls;
}
複製程式碼
綜合上面程式碼的詳細註釋,可知cls
重對映到newCls
後,newCls
的資料保留了cls
中的superclass
、cache
成員,但是bits
中指向class_rw_t
結構體地址的位域(FAST_DATA_MASK
)指向了新的class_rw_t
結構體。該結構體的ro
指標指向cls->data()
所指向的記憶體空間中儲存的class_ro_t
結構體,其他資料則是直接沿用 從namedFutureClasses
雜湊表中彈出的 future class 的class_rw_t
結構體(通過future class 的data()
方法返回)中資料。
注意:雖然
objc_class
的data()
方法宣告為返回class_rw_t *
,但是究其本質,它只是返回了objc_class
的bits
成員的FAST_DATA_MASK
標記的位域中儲存的記憶體地址,該記憶體地址實際上可以儲存任何型別的資料。在Class readClass(Class cls,bool headerIsPreoptimized)
函式中,傳入的cls
所指向的objc_class
結構體有其特殊之處:cls
的bits
成員的FAST_DATA_MASK
位域,指向的記憶體空間儲存的是class_ro_t
結構體,並不是通常的class_rw_t
。
上述只是對 future class 的重對映過程,通用的類重對映呼叫static class remapClass(Class cls)
,注意當傳入的cls
類不在remappedClasses
雜湊表中時,直接返回cls
本身;static void remapClassRef(Class *clsref)
可對傳入的Class* clsref
重對映(改變*clsref
的值),返回時clsref
將 指向*clsref
重對映後的類。類的重對映相關程式碼如下:
// 獲取remappedClasses,儲存已重對映的所有類的全域性雜湊表
static NXMapTable *remappedClasses(bool create)
{
// 靜態的全域性雜湊表,沒有找到remove介面,只會無限擴張
static NXMapTable *remapped_class_map = nil;
runtimeLock.assertLocked();
if (remapped_class_map) return remapped_class_map;
if (!create) return nil;
// remapped_class_map is big enough to hold CF’s classes and a few others
INIT_ONCE_PTR(remapped_class_map,NXCreateMapTable(NXPtrValueMapPrototype,32),NXFreeMapTable(v));
return remapped_class_map;
}
// 將oldcls重對映得到的newcls,以oldcls為關鍵字插入到remappedClasses雜湊表中
// 注意:從程式碼透露出來的資訊是,remappedClasses中只儲存 future class 重對映的類
static void addRemappedClass(Class oldcls,Class newcls)
{
runtimeLock.assertLocked();
if (PrintFuture) {
_objc_inform("FUTURE: using %p instead of %p for %s",(void*)newcls,(void*)oldcls,oldcls->nameForLogging());
}
void *old;
old = NXMapInsert(remappedClasses(YES),oldcls,newcls);
assert(!old);
}
// 獲取cls的重對映類
// 注意:當remappedClasses為空或雜湊表中不存在`cls`關鍵字,是返回`cls`本身,否則返回`cls`重對映後的類
static Class remapClass(Class cls)
{
runtimeLock.assertLocked();
Class c2;
if (!cls) return nil;
NXMapTable *map = remappedClasses(NO);
if (!map || NXMapMember(map,(void**)&c2) == NX_MAPNOTAKEY) {
return cls;
} else {
return c2;
}
}
// 對Class的指標的重對映,返回時傳入的clsref將 指向*clsref重對映後的類
static void remapClassRef(Class *clsref)
{
runtimeLock.assertLocked();
Class newcls = remapClass(*clsref);
if (*clsref != newcls) *clsref = newcls;
}
複製程式碼
最後歸納出以下結論:
- Future class 重對映返回新的類,儲存在
remappedClasses
全域性雜湊表中; - 普通類重對映返回類本身;
- 重對映的真正的目的是支援類的懶載入,懶載入類暫存為 future class 只記錄類名及 future class 屬性,在呼叫
readClass
才正式載入類資料。
3.1.3 realized class
呼叫readClass(...)
讀取類資料只是載入了類的class_ro_t
靜態資料,因此仍需要進一步配置objc_class
的class_rw_t
結構體的資料。這個過程為 class realizing,姑且稱之為認識類。具體包括:
- 配置
class_rw_t
的RW_REALIZED
、RW_REALIZING
位; - 根據
class_ro_t
的RO_META
位的值,配置class_rw_t
的version
; - 因為靜態載入的父類、元類有可能被重對映,因此要保證類的父類、元類完成class realizing;
- 配置
class_rw_t
的superclass
; - 初始化
objc_class
的isa
指標; - 配置
ivarLayout
、instanceSize
、instanceStart
。該步驟非常重要,新版本 runtime 支援 non-fragile instance variables,類的instanceStart
、instanceSize
會根據父類的instanceSize
動態調整,且需要按 WORD 對齊(TODO:後續在獨立的文章中詳細介紹); - 配置
class_rw_t
的RO_HAS_CXX_STRUCTORS
、RO_HAS_CXX_DTOR_ONLY
、RW_FORBIDS_ASSOCIATED_OBJECTS
; - 新增子類/根類;
- 將
class_ro_t
中的基本方法列表、屬性列表、協議列表,類的分類(category)中的方法列表等資訊新增到class_rw_t
中(TODO:後續在獨立的文章中詳細介紹);
實現 class realizing 的程式碼主要在static Class realizeClassWithoutSwift(Class cls)
函式中,只需要知道其大致過程即可。具體程式碼及註釋如下:
static Class realizeClassWithoutSwift(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)); // 傳入的類必須不存在於remappedClasses全域性雜湊表中
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// 若為future class,則cls的rw指向class_rw_t結構體,ro指向class_ro_t結構體,維持原狀
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING,RW_FUTURE);
} else {
// 普通類,則需要為rw分配記憶體,並將ro指標指向 傳入的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; // old runtime went up to 6
// 忽略
cls->chooseClassArrayIndex();
// 父類realizing
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
// 元類realizing
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
#if SUPPORT_NONPOINTER_ISA
// 配置RW_REQUIRES_RAW_ISA位。可忽略。
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && !(ro->flags & RO_META) &&
0 == strcmp(ro->name,"OS_object"))
{
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->superclass &&
supercls->instancesRequireRawIsa())
{
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
// 配置RW_REQUIRES_RAW_ISA位
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsa(rawIsaIsInherited);
}
#endif
// 由於存在class remapping的可能性,因此需要更新父類及元類
cls->superclass = supercls;
cls->initClassIsa(metacls);
// 調整ivarLayout
if (supercls && !isMeta) reconcileInstanceVariables(cls,supercls,ro);
// 調整instanceSize
cls->setInstanceSize(ro->instanceSize);
// 忽略
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// 忽略
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// 新增子類/根類
if (supercls) {
addSubclass(supercls,cls);
} else {
addRootClass(cls);
}
// rw中需要儲存ro中的一些資料,例如ro中的基礎方法列表、屬性列表、協議列表
// rw還需要載入分類的方法列表
methodizeClass(cls);
return cls;
}
static void methodizeClass(Class cls)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// 將ro中的基本方法列表新增到rw的方法列表中
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls,&list,1,isBundleClass(cls));
rw->methods.attachLists(&list,1);
}
// 將ro中的屬性列表新增到rw的屬性列表中
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist,1);
}
// 將ro中的協議列表新增到rw的協議列表中
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist,1);
}
if (cls->isRootMetaclass()) {
// 根元類特殊處理
addMethod(cls,SEL_initialize,(IMP)&objc_noop_imp,"",NO);
}
// 將分類中的方法列表新增到rw的方法列表中
category_list *cats = unattachedCategoriesForClass(cls,true /*realizing*/);
attachCategories(cls,cats,false /*don't flush caches*/);
if (PrintConnecting) {
if (cats) {
for (uint32_t i = 0; i < cats->count; i++) {
_objc_inform("CLASS: attached category %c%s(%s)",isMeta ? '+' : '-',cls->nameForLogging(),cats->list[i].cat->name);
}
}
}
if (cats) free(cats);
}
複製程式碼
最後總結,截止至完成 class realizing,類的載入過程大致如下圖所示。其中future class列是懶載入類(future class)的流程,經過了“新增懶載入類->載入懶載入類資訊->懶載入類重對映->認識懶載入類”四步;normal class列是普通的非懶載入類的載入流程,只經過“載入類資訊->認識類”兩個步驟。
類完成 class realizing 後,還需要執行類及分類中的load()
方法,最後在程式執行過程中第一次呼叫類的方法時(實現邏輯在IMP lookUpImpOrForward(...)
函式中)觸發isInitialized()
檢查,若未初始化,則需要先執行類的initialize()
方法。至此,類正式載入完成。
注意:最後的 class initializing 嚴格意義上應該不屬於類的載入過程,可以將其歸為獨立的類初始化階段。類的載入在
load()
方法執行後就算是完成了。
3.2 基本狀態相關行為
objc_class
結構體中類的基本狀態查詢的函式程式碼如下。注意Class getMeta()
獲取元類時:對於元類,getMeta()
返回的結果與ISA()
返回的結果不相同,對於非元類,兩者則是相同的。
bool isARC() {
return data()->ro->flags & RO_IS_ARC;
}
bool isMetaClass() {
assert(this);
assert(isRealized());
return data()->ro->flags & RO_META;
}
bool isMetaClassMaybeUnrealized() {
return bits.safe_ro()->flags & RO_META;
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
bool isRootClass() {
return superclass == nil;
}
bool isRootMetaclass() {
return ISA() == (Class)this;
}
const char *mangledName() {
assert(this);
if (isRealized() || isFuture()) {
return data()->ro->name;
} else {
return ((const class_ro_t *)data())->name;
}
}
const char *demangledName();
const char *nameForLogging();
複製程式碼
3.3 記憶體分配相關行為
根據類的資訊構建物件時,需要根據類的繼承鏈上的所有成員變數的記憶體佈局為成員變數資料分配記憶體空間,分配記憶體空間的大小固定的,並按 WORD 對齊,呼叫size_t class_getInstanceSize(Class cls)
實際是呼叫了objc_class
結構體的uint32_t alignedInstanceSize()
函式。
成員變數在例項記憶體空間中偏移量同樣也是固定的,同樣也是按 WORD 對齊。例項的第一個成員變數記憶體空間的在例項空間中的偏移量,實際是通過呼叫objc_class
結構體的uint32_t alignedInstanceStart()
函式獲取。
objc_class
結構體中涉及記憶體分配的函式程式碼如下:
// 類的例項的成員變數起始地址可能不按WORD對齊
uint32_t unalignedInstanceStart() {
assert(isRealized());
return data()->ro->instanceStart;
}
// 配置類的例項的成員變數起始地址按WORD對齊
uint32_t alignedInstanceStart() {
return word_align(unalignedInstanceStart());
}
// 類的例項大小可能因為ivar的alignment值而不按WORD對齊
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
// 配置類的例項大小按WORD對齊
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
// 獲取類的例項大小
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes. (TODO:不懂為啥)
if (size < 16) size = 16;
return size;
}
// 配置類的例項大小
void setInstanceSize(uint32_t newSize) {
assert(isRealized());
if (newSize != data()->ro->instanceSize) {
assert(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize;
}
bits.setFastInstanceSize(newSize);
}
複製程式碼
四、物件
物件的資料結構是objc_object
結構體。objc_object
僅包含一個isa_t
型別的isa
指標,和<objc/runtime>
定義的objc_object
有所不同,後者的isa
指標是Class
(指向objc_class
結構體)。這是因為新版本 runtime 支援非指標型別isa
結構,非指標型別isa
不再是指向Class
的指標而是64位二進位制位域,僅使用其中一部分位域儲存物件的類的地址,其他位賦予特殊意義主要用於協助物件記憶體管理。
objc_object
包含的方法主要有以下幾類:
-
isa
操作相關,isa
指向物件型別,在控制物件構建、物件成員變數訪問、物件訊息響應,物件記憶體管理方面有十分關鍵的作用,將在 4.1 中詳細介紹isa
; - 關聯物件(associated object)相關;
- 物件弱引用相關,物件釋放後需要通知弱引用自動置
nil
,因此物件需要知曉所有弱引用的地址; - 引用計數相關,支援物件的引用計數(reference count)管理;
-
dealloc
相關,物件析構相關,主要是釋放對關聯物件的引用; - side table 相關,side table 是 runtime 管理物件記憶體的核心資料結構,包含物件記憶體引用計數資訊、物件的弱引用資訊等關鍵資料(TODO:後續在獨立文章中介紹);
- 支援非指標型別
isa
相關;
物件的定義程式碼如下:
struct objc_object {
private:
isa_t isa;
public:
// 獲取物件型別,建立在物件不是tagged pointer的假設上
Class ISA();
// 獲取物件型別,物件可以是tagged pointer
Class getIsa();
// 初始化isa
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls,bool hasCxxDtor);
// 設定isa指向新的型別
Class changeIsa(Class newCls);
// 物件isa是否為非指標型別
bool hasNonpointerIsa();
// TaggedPointer相關,忽略
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
// 物件是否是類
bool isClass();
// 物件關聯物件相關
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// 物件弱引用相關
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
//物件是否包含 .cxx 構造/解構函式
bool hasCxxDtor();
// 引用計數相關
id retain();
void release();
id autorelease();
// 引用計數相關的實現
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// dealloc的實現
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls,bool nonpointer,bool hasCxxDtor);
id rootAutorelease2();
bool overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// 支援非指標型別isa
id rootRetain(bool tryRetain,bool handleOverflow);
bool rootRelease(bool performDealloc,bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
bool rootRelease_underflow(bool performDealloc);
void clearDeallocating_slow();
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc,bool isDeallocating,bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
size_t sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table 相關操作
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable& table);
uintptr_t sidetable_release(bool performDealloc = true);
uintptr_t sidetable_release_slow(SideTable& table,bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if DEBUG
bool sidetable_present();
#endif
};
複製程式碼
Tagged Pointer:對於一個物件引用(指標),一般情況下該引用的值為物件的記憶體地址,而tagged pointer則直接在地址中寫入物件的類和資料。
4.1 物件的 isa
objc_object
的isa
主要用於標記物件的型別。新版本 runtime 的isa
支援兩種形式:指標型別、非指標型別。前者簡單指向物件的類。後者為64位二進位制位域,當然其中也包括物件的類的地址,其他位域都有其特殊含義。為支援兩種形式,runtime 使用isa_t
聯合體儲存物件的isa
。
注意:Union 聯合體的成員之間共享記憶體空間。以
isa_t
為例,cls
成員和bits
成員雖然不同,但是兩者的值實際在任何時候都是一致的。例如,isa.class = [NSString class]
指定了cls
指向NSString
類的記憶體地址,此時檢視isa.bits
會發現其值為NSString
類的記憶體地址;反之,isa.bits = 0xFF
,則isa.class
的值也變為255
。
4.1.1 isa_t 聯合體
isa_t
聯合體有兩個成員Class cls
、uintptr_t bits
,兩者共享8個位元組的記憶體空間(64位機)。以下為isa_t
的原始碼,刪除了其中 x86_64 及其他架構下的程式碼。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
// 需要編譯選項支援非指標型別isa
#if SUPPORT_NONPOINTER_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# endif
// SUPPORT_NONPOINTER_ISA
#endif
};
複製程式碼
下面是 arm64 架構下isa_t
的bits
的位域分佈圖示,左高位右低位。下面對各個位域的解析中有多次提及 side table(TODO:後續獨立文章介紹),該結構用於對記憶體中的所有 Objective-C 物件進行統一的記憶體管理,其中最重要的是物件記憶體計數管理、物件弱指標管理。
-
indexed
:洋紅區域右起第1位。0
表示isa
為指標型別,儲存類的地址;1
表示isa
為非指標型別。之所以使用最低位區分isa
型別,是因為當isa
為Class
時,其本質是指向objc_class
結構體首地址的指標,由於objc_class
必定按WORD對齊,即地址必定是8的整數倍,因此指標型別的isa
的末尾3位必定全為0
; -
has_assoc
:洋紅區域右起第2位。標記物件是否存在關聯物件; -
has_cxx_dtor
:洋紅區域右起第3位。標記物件是否存在cxx
語系的解構函式。使用指標型別isa
的物件,該標記儲存在 side table 中; -
shiftcls
:紅色區域共33位。儲存類的虛擬記憶體地址,標記物件的型別(核心資料); -
magic
:黃色區域共6位。用於非指標型別的isa
校驗,arm64架構下這6位為固定值0x1a
; -
weakly_referenced
:青色區域右起第1位。標記物件是否被弱引用。使用指標型別isa
的物件,該標記儲存在 side table 中; -
deallocating
:青色區域右起第2位。標記物件是否已執行析構。使用指標型別isa
的物件,該標記儲存在 side table 中; -
has_sidetable_rc
:青色區域右起第3位。標記是否聯合 side table 儲存該物件的引用計數; -
extra_rc
:綠色區域共19位。記錄物件引用計數,在has_sidetable_rc
為1
時,需要聯合 side table 才能獲取物件的確切引用計數;
注意:MSB是Most Significant Bit指最高有效位,
extra_rc
需要處理上溢位情況因此為MSB,LSB是Least Significant Bit,indexed
位用來判斷isa
指標的型別因此為LSB。
4.1.2 物件的 isa 相關操作
4.1.2.1 isa 的構建
物件構建時,需要直接或間接呼叫objc_object
的initIsa(Class cls,bool hasCxxDtor)
構建isa
,其他initIsa
方法均在內部呼叫了該方法。其中cls
引數表示物件的類,nonpointer
表示是否構建非指標型別isa
,hasCxxDtor
表示物件是否存在cxx
語系解構函式。
inline void
objc_object::initIsa(Class cls,bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
// 構建非指標型別isa
isa.cls = cls;
} else {
// 構建非指標型別isa
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
// 當前主流機型一般會執行到這個邏輯分支
newisa.bits = ISA_MAGIC_VALUE; // magic設定為0xA1,index設定為1
newisa.has_cxx_dtor = hasCxxDtor;
// shiftcls位域儲存物件的類的地址,注意最低3位不需要儲存,因為必定是全0
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// 指向新的newisa
isa = newisa;
}
}
複製程式碼
4.1.2.2 isa 的應用
非指標型別isa
實際是將 side table 中部分記憶體管理資料(包括部分記憶體引用計數、是否包含關聯物件標記、是否被弱引用標記、是否已析構標記)轉移到isa
中,從而減少objc_object
中記憶體管理相關操作的 side table 查詢數量。objc_object
中關於狀態查詢的方法,大多涉及到isa
的位操作。方法數量有點多不一一列舉,本節只以弱引用相關查詢為例。
-
isWeaklyReferenced()
用於查詢物件是否被弱引用。當物件isa
為非指標型別時,直接返回isa.weakly_referenced
,否則需要呼叫sidetable_isWeaklyReferenced ()
從 side table 中查詢結果; -
setWeaklyReferenced_nolock()
用於設定物件是否被弱引用。當物件isa
為非指標型別時,僅需將weakly_referenced
位置為1
,否則需要呼叫sidetable_setWeaklyReferenced_nolock()
從 side table 中查詢結果並寫入。
inline bool
objc_object::isWeaklyReferenced()
{
assert(!isTaggedPointer());
if (isa.nonpointer) return isa.weakly_referenced;
else return sidetable_isWeaklyReferenced();
}
inline void
objc_object::setWeaklyReferenced_nolock()
{
// 原始碼設定weakly_referenced過程比較繁雜
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
sidetable_setWeaklyReferenced_nolock();
return;
}
if (newisa.weakly_referenced) {
ClearExclusive(&isa.bits);
return;
}
newisa.weakly_referenced = true;
if (!StoreExclusive(&isa.bits,oldisa.bits,newisa.bits)) goto retry;
}
複製程式碼
4.2 物件的構建
物件的構建本質上都通過呼叫_class_createInstanceFromZone(...)
函式實現,其中最關鍵的傳入引數是Class
型別的cls
,含義是構建類為cls
的物件。程式碼看起來挺長,實際上僅包含兩個操作:
- 為物件分配
cls->instanceSize()
大小的記憶體空間; - 構建物件的
isa
;
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls,size_t extraBytes,void *zone,bool cxxConstruct = true,size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
// ----------- 邏輯分支1 ----------- //
// 1.1 分配物件記憶體
obj = (id)calloc(1,size);
if (!obj) return nil;
// 1.2 構建物件isa
obj->initInstanceIsa(cls,hasCxxDtor);
}
else {
// ----------- 邏輯分支2 ----------- //
// 2.1 分配物件記憶體
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone,size);
} else {
obj = (id)calloc(1,size);
}
if (!obj) return nil;
// 2.2 構建物件isa
obj->initIsa(cls);
}
// 若存在cxx語系建構函式,則呼叫。可忽略
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj,cls);
}
return obj;
}
複製程式碼
4.3 物件的析構
物件的析構呼叫物件的rootDealloc()
方法,原始碼雖然不很多但是整個過程經過了幾個函式。總結物件析構所需要的操作如下:
- 釋放對關聯物件的引用;
- 清空 side table 中儲存的該物件弱引用地址、物件引用計數等記憶體管理資料;
- 釋放物件佔用的記憶體;
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
// 釋放物件佔用記憶體
free(this);
}
else {
object_dispose((id)this);
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
// 釋放物件佔用記憶體
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// 若存在cxx語系解構函式,則呼叫。可忽略
if (cxx) object_cxxDestruct(obj);
// 釋放關聯物件
if (assoc) _object_remove_assocations(obj);
// 清空side table中儲存的該物件的弱引用地址、及引用計數等記憶體管理資料
obj->clearDeallocating(); // 可忽略實現細節
}
return obj;
}
複製程式碼
五、總結
-
Runtime 中物件用
objc_object
實現,用isa_t
型別佔用8位元組記憶體空間的isa
成員指向物件的類,新版本 runtime 的isa
還儲存了物件引用計數、是否已析構、是否被弱引用、是否存在關聯物件等物件記憶體管理的關鍵資料; -
Runtime 中類用
objc_class
實現,類也是一個物件,objc_class
的isa
指向類的元類,元類的isa
指向根元類,根元類的isa
指向根元類自身,該條件可以用於判斷類是否為根元類; -
objc_class
的superclass
成員指向類的父類,用於組織類的繼承鏈; -
objc_class
的資料儲存在bits
成員的有效位域指向的記憶體空間中,類的編譯時決議資料儲存在class_ro_t
結構體中,執行時決議資料儲存在class_rw_t
結構體中,class_ro_t
、class_rw_t
的flags
成員用於標記類的狀態,資料總入口為class_rw_t
; -
類存在懶載入機制,懶載入類先標記為 future class,正式載入 future class 資料需要呼叫
readClass(...)
方法,對 future class 進行重對映(remapping); -
從映象載入的類由於只包含編譯時決議資料,因此
bits
成員指向class_ro_t
資料結構。必須經過 class realizing,構建類的class_rw_t
資料,調整類的instanceSize
、instanceStart
、ivarLayout
(為了支援 non-fragile instance variables),以及將class_ro_t
中的基本方法列表、屬性列表、協議列表,類的分類(category)中的方法列表等資訊新增到class_rw_t
中; -
類的成員變數、方法列表、屬性列表、分類的實現及載入、物件記憶體管理涉及的 side table 將在後續獨立文章中詳細介紹。