一啃匿、Class的本質(zhì)
下列代碼是仿照objc_class結(jié)構(gòu)體晃择,提取其中需要使用到的信息,自定義的一個結(jié)構(gòu)體姆怪。
#import <Foundation/Foundation.h>
#ifndef XXClassInfo_h
#define XXClassInfo_h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance對象占用的內(nèi)存空間
#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;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 屬性列表
const protocol_list_t * protocols; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t *data() {
// 提供data()方法進(jìn)行 & FAST_DATA_MASK 操作
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC對象 */
struct xx_objc_object {
void *isa;
};
/* 類對象 */
struct xx_objc_class : xx_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
// 提供metaClass函數(shù)叛赚,獲取元類對象
xx_objc_class* metaClass() {
// isa指針需要經(jīng)過一次 & ISA_MASK操作之后才得到真正的地址
return (xx_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* XXClassInfo_h */
根據(jù)結(jié)構(gòu)體中的內(nèi)容及其關(guān)系,總結(jié)如下圖:
可以看出稽揭,每個類都對應(yīng)有一個class_rw_t結(jié)構(gòu)體俺附,class_rw_t結(jié)構(gòu)體內(nèi)有一個指向class_ro_t結(jié)構(gòu)體的指針。在編譯期間溪掀,class_ro_t結(jié)構(gòu)體就已經(jīng)確定事镣,objc_class中的bits的data部分存放著該結(jié)構(gòu)體的地址。在runtime運行之后揪胃,具體說來是在運行runtime的realizeClass方法時璃哟,會生成class_rw_t結(jié)構(gòu)體,該結(jié)構(gòu)體包含了class_ro_t只嚣,并且更新data部分沮稚,換成class_rw_t結(jié)構(gòu)體的地址。細(xì)看兩個結(jié)構(gòu)體的成員變量會發(fā)現(xiàn)很多相同的地方册舞,他們都存放著當(dāng)前類的屬性、實例變量障般、方法调鲸、協(xié)議等等。區(qū)別在于:class_ro_t存放的是編譯期間就確定的挽荡;而class_rw_t是在runtime時才確定藐石,它會先將class_ro_t的內(nèi)容拷貝過去,然后再將當(dāng)前類的分類的這些屬性定拟、方法等拷貝到其中于微。所以可以說class_rw_t是class_ro_t的超集逗嫡,當(dāng)然實際訪問類的方法、屬性等也都是訪問的class_rw_t中的內(nèi)容株依。
二驱证、isa的本質(zhì)
OC對象在內(nèi)存中的排布是一個結(jié)構(gòu)體,其大致框架如下圖:
每個對象結(jié)構(gòu)體的首個成員是個Class類型的變量,該變量定義了對象所屬的類,通常稱為isa指針恋腕。在arm64位下的iOS操作系統(tǒng)中抹锄,OC對象的isa區(qū)域不再只是一個指針,需要經(jīng)過一次位運算之后才得到真正的地址荠藤。用 64 bit 存儲一個內(nèi)存地址顯然是種浪費伙单,畢竟很少有那么大內(nèi)存的設(shè)備。于是可以優(yōu)化存儲方案哈肖,用一部分額外空間存儲其他內(nèi)容吻育。isa 指針第一位為 1 即表示使用優(yōu)化的 isa 指針,這里列出不同架構(gòu)下的 64 位環(huán)境中 isa 指針結(jié)構(gòu):
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_NONPOINTER_ISA
# if __arm64__
# define ISA_MASK 0x00000001fffffff8ULL
# define ISA_MAGIC_MASK 0x000003fe00000001ULL
# define ISA_MAGIC_VALUE 0x000001a400000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000
uintptr_t magic : 9;
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 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 14;
# define RC_ONE (1ULL<<50)
# define RC_HALF (1ULL<<13)
};
# else
// Available bits in isa field are architecture-specific.
# error unknown architecture
# endif
// SUPPORT_NONPOINTER_ISA
#endif
};
下面是一些位所代表的的含義
三淤井、對象扫沼,類對象,元類對象的關(guān)系
上圖所示即為對象庄吼、類對象缎除、元類對象之間的關(guān)系,總結(jié)如下:
1.每一個對象中都包含一個isa對象总寻。
2.實例的isa指針指向類器罐,類是一個objc_class結(jié)構(gòu)體,包含實例的方法列表渐行、參數(shù)列表轰坊、category等,除此之外祟印,objc_class中還有一個super_class肴沫,指向其類的父類。
3.類的isa指針指向元類蕴忆,即metaClass颤芬,元類存儲類方法等信息。元類里也包含isa指針套鹅,元類里的isa指針指向根元類站蝠,根元類的isa指針指向自己。
4.obj_msgSend發(fā)送實例消息的時候卓鹿,先找到實例菱魔,然后通過實例的isa指針找到類的方法列表及參數(shù)列表等,如果找到則返回吟孙,如果沒有找到澜倦,則通過super_class在其父類中重復(fù)此過程聚蝶。
5.obj_msgSend發(fā)送類消息的時候,通過類的isa找到元類藻治,然后流程與步驟4相同碘勉。
四、消息傳遞機(jī)制
OC是一門非常動態(tài)的語言栋艳,以至于確定調(diào)用哪個方法被推遲到了運行時恰聘,而非編譯時。與之相反吸占,C語言使用靜態(tài)綁定晴叨,也就是說,在編譯期就能決定程序運行時所應(yīng)該調(diào)用的函數(shù)矾屯,所以在C語言中兼蕊,如果某個函數(shù)沒有實現(xiàn),編譯時是不能通過的件蚕。而OC是相對動態(tài)的語言孙技,運行時還可以向類中動態(tài)添加方法,所以編譯時并不能確定方法到底有沒有對應(yīng)的實現(xiàn)排作,編譯器在編譯期間也就不能報錯牵啦。
對象的方法調(diào)用用OC的術(shù)語來講叫做“給某個對象發(fā)送某條消息”。在運行時妄痪,編譯器會把方法調(diào)用轉(zhuǎn)化為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用哈雏,即objc_msgSend(),該函數(shù)是運行時消息傳遞機(jī)制中的核心函數(shù)衫生。
對象的方法調(diào)用步驟如下:
1.實例對象的方法調(diào)用要先通過實例的isa指針找到類裳瘪,隨后去該類的方法 cache 中查找,如果找到了就返回它罪针。
2.如果沒有找到彭羹,就去該類的方法列表中查找。如果在該類的方法列表中找到了泪酱,則將 IMP 返回派殷,并將它加入cache中緩存起來。根據(jù)最近使用原則西篓,這個方法再次調(diào)用的可能性很大愈腾,緩存起來可以節(jié)省下次調(diào)用再次查找的開銷。
3.如果在該類的方法列表中沒找到對應(yīng)的 IMP岂津,再通過該類結(jié)構(gòu)中的 super_class指針在其父類的方法 cache和方法列表中查找。當(dāng)在某個父類的方法 cache或方法列表中找到對應(yīng)的 IMP悦即,就返回它吮成,否則就繼續(xù)循環(huán)橱乱,直到基類。
4.如果在自身以及所有父類的方法 cache和方法列表中都沒有找到對應(yīng)的 IMP粱甫,則進(jìn)入消息轉(zhuǎn)發(fā)流程泳叠。
5.類對象的方法調(diào)用要通過類的isa找到元類,隨后到元類及其所有父類的方法 cache 和方法列表中進(jìn)行查找茶宵,流程與步驟1~4相同危纫。
五、消息轉(zhuǎn)發(fā)機(jī)制
消息傳遞過程中會在相關(guān)的類對象中搜索方法列表乌庶,如果找不到則會沿著繼承樹向上一直搜索直到繼承樹根部(通常為NSObject)种蝶,如果還是找不到就進(jìn)行消息轉(zhuǎn)發(fā),如果消息轉(zhuǎn)發(fā)失敗了就會執(zhí)行doesNotRecognizeSelector:方法報unrecognized selector錯瞒大。消息轉(zhuǎn)發(fā)主要分三步:動態(tài)方法決議螃征、備用接收者、完整消息轉(zhuǎn)發(fā)透敌,流程如下: