運(yùn)行時源碼版本 objc4-750.1
OC中的id(實例對象)以及NSObject(類)到底是什么
//在objc-private.h文件中
typedef struct objc_class *Class;
typedef struct objc_object *id;
//在objc-private.h文件中
struct objc_object {
private:
isa_t isa; //共用體類型, 具體的定義在上面
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
*** 省略 ***
};
//在objc-runtime-new.h文件中
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();
}
*** 省略 ***
};
通過代碼可以看出平時我們的實例對象, 類都是都是C語言結(jié)構(gòu)體, 并且objc_class是繼承與objc_object. 也就是說其實類也某一個 '類' 的實例對象, 這個特殊的 '類' 我們稱之為 元類
元類(meta class)
通過上面的代碼我們可以確定Objective-C 中類也是一個對象, 我們把類對象所屬的類型稱之為元類(meta class), 元類(meta class)也是一個叫做根元類(root meta class)的實例,那么有了元類的好處是啥呢?
我們知道實例對象的方法是定義在它所屬的類當(dāng)中的,那么類方法自然是定義在元類當(dāng)中的. 通過這個元類可以保證無論是類還是對象都能通過相同的機(jī)制查找方法的實現(xiàn)待逞。
從圖中可以看出來對象的isa指向它所屬的類,元類的isa都指向根元類(root meta class), 而根元類的isa則指向了自己, 這樣就形成了一個閉環(huán)的結(jié)構(gòu).
當(dāng)實例方法被調(diào)用時甥角,它可以通過持有的 isa 來查找對應(yīng)的類,然后在這里的 class_data_bits_t 結(jié)構(gòu)體中查找對應(yīng)方法的實現(xiàn)识樱。同時嗤无,每一個 objc_class 也有一個指向自己的父類的指針 super_class 用來查找繼承的方法震束。
class_data_bits_t 中存有 Class 的對應(yīng)方法,具體如何存儲及查找以后會另做分析
isa_t isa(共用體類型的isa)
從上面objc_object的定義以及objc_class是繼承與objc_object的關(guān)系我們可以得到如下圖的關(guān)系
所有繼承自 NSObject 的類實例化后的對象都會包含一個類型為 isa_t 的共用體.
那么 isa 到底是什么呢当犯?其實在 ARM 64 之前垢村,isa 直接保存了類對象或者元類對象的地址,而之后使用了位域結(jié)構(gòu)存儲了更多信息嚎卫。
//64位之后的isa定義
//在objc-private.h文件中
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
看一下ISA_BITFIELD定義
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 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)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
上面列出了ISA_BITFIELD在arm64架構(gòu)和x86_64架構(gòu)下的定義, iOS 應(yīng)用為 arm64 架構(gòu)環(huán)境拓诸。由于我的測試代碼是基于 macOS 的所以我們下面分析的時候看的是 x86_64 架構(gòu)下的定義, 這兩種結(jié)構(gòu)的實現(xiàn)和位數(shù)可能有些差別,但這些字段都是存在的.所以不會影響我們對isa的理解.
// __x86_64__架構(gòu)
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
先看一下每一個參數(shù)的含義
變量名 | 所占位數(shù) | 含義 |
---|---|---|
nonpointer | 1 | 0 表示普通的 isa 指針,1 表示使用優(yōu)化倍谜,存儲引用計數(shù) |
has_assoc | 1 | 表示該對象是否包含 associated object尔崔,如果沒有,則析構(gòu)時會更快 |
has_cxx_dtor | 1 | 表示該對象是否有 C++ 或 ARC 的析構(gòu)函數(shù)烙常,如果沒有鹤盒,則析構(gòu)時更快 |
shiftcls | 44 | 類的指針 |
magic | 6 | 固定值侦锯,用于在調(diào)試時分辨對象是否未完成初始化 |
weakly_referenced | 1 | 表示該對象是否有過 weak 對象尺碰,如果沒有亲桥,則析構(gòu)時更快 |
deallocating | 1 | 表示該對象是否正在析構(gòu) |
has_sidetable_rc | 1 | 表示該對象的引用計數(shù)值是否過大無法存儲在 isa 指針 |
extra_rc | 8 | 存儲引用計數(shù)值減一后的結(jié)果 |
isa是一個共用體,共用體的所有成員占用同一段內(nèi)存.而isa總共占用的內(nèi)存是64位, 表中的數(shù)字代表每個變量占用的位數(shù).
cache_t cache(方法緩存)
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
通過源碼我們可以知道cache_t是有一個bucket_t類型的結(jié)構(gòu)體和兩個uint32_t類型的變量組成题篷。
- bucket_t是一個散列表番枚,用來存儲類中的方法的鏈表,緩存曾經(jīng)調(diào)用過的方法葫笼,可以調(diào)高方法的查找速度
- _mask是表示分配用來緩存buckets的總數(shù)
- _occupied是表明當(dāng)前實際占用緩存buckets的個數(shù)
其實cache主要的作用就是為了提高調(diào)用方法的效率提升性能路星。
- 沒有cache機(jī)制:當(dāng)對象調(diào)用方法的時候,先根據(jù)對象的isa指針去它對應(yīng)的類中尋找方法呈昔,然后在類的methodLists中尋找垫挨,如果沒有找到九榔,則通過super_class指針到父類中的methodList里面去找,一旦找到方法就調(diào)用剩蟀,沒有找到的話就進(jìn)行消息轉(zhuǎn)發(fā)切威,或者報出異常。(效率很低)
- 有cache機(jī)制:當(dāng)對象調(diào)用方法的時候先朦,先去cache中尋找方法喳魏,如果沒有找到,再依照上述方法到methodLists中查找迷郑。
<<<<<<<<<<<<<< 后續(xù)持續(xù)更新 >>>>>>>>>>>>>>>>