從runtime源碼中看到Class的結(jié)構(gòu)如下
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();
}
......
}
// bits.data();
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
分別解釋下幾個(gè)字段
- superclass:指向父類的指針
- cache:調(diào)用過(guò)的方法緩存
- bits:用于獲取具體的類信息
- class_rw_t:類具體信息的結(jié)構(gòu)體癣亚,可以看到是由bits & FAST_DATA_MASK得到
接著看看class_rw_t包含哪些信息嘉冒,class_rw_t結(jié)構(gòu)如下:
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; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
......
};
class_rw_t中包含了方法侧漓、屬性蚕礼、協(xié)議等乎串,還有個(gè)ro历恐,這個(gè)ro指向一個(gè)class_ro_t對(duì)象荸恕,class_ro_t里面包含了類初始的信息,是只讀的铛嘱。
class_ro_t的結(jié)構(gòu)如下:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance對(duì)象占用的內(nèi)存空間大小
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //類名
method_list_t * baseMethodList; //初始方法列表
protocol_list_t * baseProtocols; //初始協(xié)議列表
const ivar_list_t * ivars; // 成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; //初始協(xié)議列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
class_rw_t中methods暖释、properties、protocols是二維數(shù)組墨吓,是可讀可寫(xiě)的,包含類初始以及分類的方法纹磺、屬性帖烘、協(xié)議。最開(kāi)始是沒(méi)有class_rw_t橄杨,class_rw_t是在運(yùn)行時(shí)創(chuàng)建的秘症,并將class_ro_t的內(nèi)容和分類的內(nèi)容添加進(jìn)來(lái);
上面的結(jié)論可以從下面runtime的源碼中看出式矫,刪減了部分代碼乡摹,只保留了上述流程:
/***********************************************************************
* realizeClass
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{
runtimeLock.assertWriting();
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));
ro = (const class_ro_t *)cls->data(); //開(kāi)始時(shí)bits是指向ro的
if (ro->flags & RO_FUTURE) {
// 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.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw); // 創(chuàng)建完rw將ro賦值給rw的ro,并將rw賦值給cls的bits
}
// Attach categories
methodizeClass(cls);
return cls;
}
從上面源碼注釋可以看出這個(gè)函數(shù)是類第一次初始化時(shí)執(zhí)行采转,最初是沒(méi)有rw的聪廉,class的bits是指向ro的。rw創(chuàng)建完將ro賦值給rw的ro故慈,并將rw賦值給cls的bits板熊,最后從注釋可以看出是處理分類的內(nèi)容;
函數(shù)methodizeClass源碼如下:
static void methodizeClass(Class cls)
{
runtimeLock.assertWriting();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
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);
}
// 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_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
從上述源碼中可以看出察绷,從rw找到ro取出里面的baseMethods干签、baseProperties、baseProtocols拆撼,添加到rw對(duì)應(yīng)的methods容劳、properties、protocols中闸度。最后取出未添加過(guò)的分類內(nèi)容添加進(jìn)來(lái)竭贩。關(guān)于怎么將分類方法添加進(jìn)來(lái)的,即attachCategories函數(shù)的具體實(shí)現(xiàn)筋岛,請(qǐng)查看About Category娶视。
方法的緩存
方法的調(diào)用如果每次都去類、父類、元類中一層層查找效率是比較低肪获,所以runtime中對(duì)調(diào)用過(guò)的方法進(jìn)行了緩存寝凌,放在類的cache中;
講方法緩存前先來(lái)了解下方法的底層結(jié)構(gòu):
struct method_t {
SEL name; //函數(shù)名
const char *types; //編碼(返回值類型孝赫、參數(shù)類型)
IMP imp; //指向函數(shù)的指針(函數(shù)地址)
};
- imp:函數(shù)的具體實(shí)現(xiàn)
typedef id (*IMP)(id, SEL, ...);
- SEL:代表方法\函數(shù)名较木,也叫方法選址器,底層結(jié)構(gòu)跟char *類似;
- types:包含返回值和參數(shù)編碼的字符串
iOS提供了@encode的指令青柄,可將具體的類型表示成字符串編碼伐债,見(jiàn)下表(部分code)
code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long |
q | A long long |
c | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
V | A void |
* | A charactor string(char *) |
@ | An object(whether statically typed or typed id) |
: | A method selector(SEL) |
^type | A pointer to type |
舉個(gè)例子:types = "i20@0:8i16",OC中方法默認(rèn)會(huì)傳入id類型的self和SEL ,代表的含義如下
返回值類型 | 參數(shù)總長(zhǎng)度 | 參數(shù)1類型及開(kāi)始位置 | 參數(shù)2類型及開(kāi)始位置 | 參數(shù)3 類型及開(kāi)始位置 |
---|---|---|---|---|
int類型 | 20 | id 類型 | SEL | int類型 |
先來(lái)看下緩存cache_t的結(jié)構(gòu):
struct cache_t {
struct bucket_t *_buckets; //哈希表致开,存儲(chǔ)調(diào)用方法
mask_t _mask; // 哈希表長(zhǎng)度 - 1
mask_t _occupied; //已經(jīng)緩存的數(shù)量
}
struct bucket_t {
cache_key_t _key; // SEL作為key
IMP _imp; //函數(shù)的內(nèi)存地址
}
Class的方法緩存(cache_t)是用哈希表實(shí)現(xiàn)的峰锁,可以提高方法的查找效率
散列表(Hash table,也叫哈希表)双戳,是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)虹蒋。也就是說(shuō),它通過(guò)把關(guān)鍵碼值映射到表中一個(gè)位置來(lái)訪問(wèn)記錄飒货,以加快查找的速度魄衅。這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表塘辅。
給定表M晃虫,存在函數(shù)f(key),對(duì)任意給定的關(guān)鍵字值key扣墩,代入函數(shù)后若能得到包含該關(guān)鍵字的記錄在表中的地址哲银,則稱表M為哈希(Hash)表,函數(shù)f(key)為哈希(Hash) 函數(shù)沮榜。
一般哈希表是通過(guò)目標(biāo)key &或者%上一個(gè)值盘榨,得到一個(gè)哈希表位置的下標(biāo);類方法緩存是用方法SEL作為key & _mask得到一個(gè)位置下標(biāo)蟆融,然后將方法地址存入哈希表草巡;哈希表下標(biāo)從0開(kāi)始,最大為哈希表長(zhǎng)度減一型酥,這也是_mask大小為哈希表長(zhǎng)度減一的原因山憨;
下面是方法緩存函數(shù)的源碼:
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
// 說(shuō)明當(dāng)緩存達(dá)到散列表的3/4時(shí)就會(huì)擴(kuò)容
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
//找到第一個(gè)可用的位置,插入弥喉。表最小長(zhǎng)度為4并且在達(dá)到3/4容量時(shí)擴(kuò)容郁竟,確保表一定有可用的位置
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();// 表中沒(méi)存過(guò)該方法_occupied自增
bucket->set(key, imp);
}
從上述源碼中可以看出散列表最小長(zhǎng)度為4并且在達(dá)到3/4容量時(shí)擴(kuò)容,確保表一定有可以插入的位置由境。插入前棚亩,查找表中是否已經(jīng)存儲(chǔ)過(guò)該方法蓖议,沒(méi)存過(guò)_occupied加一。
現(xiàn)在看下查找緩存方法的函數(shù):
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m); //key & mask得到的下標(biāo)
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
// arm64架構(gòu)下的cache_next
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
因?yàn)椴煌琸ey & mask得到的下標(biāo)可能相同讥蟆,所以通過(guò)key & mask得到初始下標(biāo)后勒虾,拿到散列表下標(biāo)對(duì)于的bucket對(duì)象取出key與目標(biāo)key比較,相等表示就是我們想要的bucket瘸彤。不同就循環(huán)查看i - 1的位置修然,當(dāng)i=0時(shí)查看mask位置的bucket是否是想要的;如果遍歷完之后還沒(méi)找到正確的质况,做一些錯(cuò)誤處理愕宋,具體查找cache_t::bad_cache函數(shù)查看。