objc-runtime 開源地址
由于OC是屬于C的超集再加上runtime的存在,我們寫的每一個(gè)OC方法在編譯階段被轉(zhuǎn)成
id objc_msgSend(id self, SEL op, ...)
關(guān)于它的實(shí)現(xiàn)已經(jīng)有大神提供了C語言版本的實(shí)現(xiàn)由于每個(gè)OC方法都會(huì)轉(zhuǎn)換成這個(gè)函數(shù)調(diào)用,所以它的高效性顯得尤為重要。
關(guān)于objc_msgSend的實(shí)現(xiàn)過程,上篇文章其實(shí)我們也有提到過,歸根到底,就是利用SEL去尋找IMP,執(zhí)行目標(biāo)函數(shù)。
我們來分析一下這個(gè)"尋根"的過程:
YY大神在他的博客中提到:
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //調(diào)用這個(gè)函數(shù),偽代碼.
}
class_getMethodImplementation他的實(shí)現(xiàn)在源碼里是可以找到的。
- 第一步調(diào)用這個(gè)函數(shù)
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
然后主要是這個(gè)函數(shù):
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
這個(gè)方法的執(zhí)行過程很有趣:
// 先從cache里檢查是否存在
if (cache) {
imp = cache_getImp(cls, sel); // 此方法在上面??大神提供的C語言實(shí)現(xiàn)中,有具體實(shí)現(xiàn)。
if (imp) return imp;
}
// cache尋找
cls = self->isa;
cache = cls->cache;
hash = cache->mask;
index = (unsigned int) _cmd & hash;
do{
method = cache->buckets[ index];
if(!method) goto recache;
index = (index + 1) & cache->mask;
}while( method->method_name != _cmd);
return( (*method->method_imp)( (id) self, _cmd));
為了讀懂上面的代碼。這里不得不提的事class的源碼結(jié)構(gòu):盜Vanney大神的圖

isa superclass 這二者的作用很容易理解。
cache 緩存的方法列表
class_data_bits_t bits這個(gè)結(jié)構(gòu)體非常重要!它存儲(chǔ)了非常多的信息,包括編譯時(shí)確定的類的變量信息,方法列表,協(xié)議方法列表,weak表... 關(guān)于它我們稍后再談。
現(xiàn)在我們知道了Class類型的內(nèi)部結(jié)構(gòu),回頭我們?cè)倏聪聞偛诺南⒄{(diào)用。
cls = self->isa; // 通過isa指針拿到當(dāng)前對(duì)象的classcache = cls->cache; // 通過class拿到cache—方法緩存列表hash = cache->mask; index = (unsigned int) _cmd & hash; // 通過cmd和cache掩碼的與運(yùn)算獲取method在map表中的序列號(hào)(這里我們可以看到,哈希表中其實(shí)存儲(chǔ)的是method即SEL和IMP的映射關(guān)系),進(jìn)而拿到最終的IMP指針。
struct method_t {
SEL name;
const char *types;
IMP imp;
}
通過這一系列的操作我們最終獲取到了函數(shù)的地址。但這僅僅是從方法緩存中獲取方法。那么,如果cache里沒有對(duì)應(yīng)的IMP呢?
在回到IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)函數(shù),往下走會(huì)代碼會(huì)執(zhí)行到這一句:
它主要對(duì)類創(chuàng)建了真正的運(yùn)行時(shí)環(huán)境( rwlock_writer_t lock(runtimeLock); 保證線程安全)。
if (!cls->isRealized()) {
rwlock_writer_t lock(runtimeLock);
realizeClass(cls);
}
具體實(shí)現(xiàn):
static Class realizeClass(Class cls){
...
// 中間代碼
ro = (const class_ro_t *)cls->data();
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);
}
...
}
到了這里我們可以接著看class_data_bits_t結(jié)構(gòu)體了:
// 只截取了部分 源碼
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
bits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
}
bool isSwift() {
return getBit(FAST_IS_SWIFT);
}
}
這個(gè)結(jié)構(gòu)體中只有一個(gè)變量 uintptr_t bits; 它是一個(gè)擁有指針存儲(chǔ)功能的unsigned long 類型。它只有64位大小,指針存儲(chǔ)結(jié)構(gòu)如下:

通過與bits與對(duì)應(yīng)flag的按位運(yùn)算得到對(duì)應(yīng)的指針地址。比如:
bool isSwift() {
return getBit(FAST_IS_SWIFT);
}
#define FAST_IS_SWIFT (1UL<<0)與上圖的結(jié)構(gòu)一直,第一位標(biāo)識(shí)位儲(chǔ)存是否是Swift語言的flag(由編譯器設(shè)置)。
不過,最關(guān)鍵的還是下面這個(gè):
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
它的data函數(shù),返回一個(gè)class_rw_t 類型的結(jié)構(gòu)體指針。
根據(jù)上面的數(shù)據(jù)結(jié)構(gòu)分布圖,bits 里有44位儲(chǔ)存著class_rw_t。
這一點(diǎn)可以在#define FAST_DATA_MASK 0x00007ffffffffff8UL里解釋。
然而,在源碼中,ro = (const class_ro_t *)cls->data();class的data即:
class_rw_t *data() {
return bits.data(); // 調(diào)用的上面的函數(shù)
}
被強(qiáng)轉(zhuǎn)成了const class_ro_t *這是為什么呢?
其實(shí),在runtime調(diào)用之前,編譯之后,bits.data()也就是bits的class_rw_t data是指向const class_ro_t結(jié)構(gòu)的。
我們?cè)賮砜?code>class_ro_t的結(jié)構(gòu)
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;
}
};
這里面正真存儲(chǔ)了,class在編譯時(shí)期就確定的屬性,方法,協(xié)議等等。
而且這里的
method_list_t * baseMethodList;
const ivar_list_t * ivars;
都是基于entsize_list_tt實(shí)現(xiàn),保證了它們?cè)趓untime期間的不可變性。
同時(shí)我們?cè)谶@里也可以順便解釋下分類方法的加載過程,為什么在分類中不能添加成員變量的問題:
我們創(chuàng)建的分類其實(shí)在源碼中屬于另一種類型:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
可以看到,它其實(shí)是沒有isa指針的!但這并不能解釋不能添加變量的問題。
我們要從它的裝載過程說起。
在app啟動(dòng)后,系統(tǒng)會(huì)調(diào)用load_images的方法,來加載各種庫文件,當(dāng)然就包括runtime庫,下面是objclib的加載過程:
_objc_init
└──map_2_images
└──map_images_nolock
└──_read_images
當(dāng)執(zhí)行到_read_images的時(shí)候,我們可以在源碼中找到實(shí)現(xiàn)過程
線從boundle里獲取class目錄。
然后我們會(huì)發(fā)現(xiàn),在這里調(diào)用了realizeClass(cls);方法!為類開辟了runtime預(yù)備環(huán)境(將bits的data重新指向了class_rw_t類型,并且將class_ro_t放入了class_rw_t的ro變量中)。
做完這些之后,在是對(duì)categories的處理。
真正實(shí)現(xiàn)分類中的類attach到class的方法是:
static void
attachCategories(Class cls, category_list *cats, bool flush_caches);
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
可以看到,分類中的method等是被賦予到了,cls->data()中,這是cls->data()指向的是class_rw_t類型。
而在category_t中只有property_list_t沒有ivar_list_t, 并且在class_rw_t ro 中的ivar_list_t又是只讀的,所以分類中的屬性是不會(huì)生成實(shí)例變量的(但是可以利用另一種方法變相實(shí)現(xiàn)“添加變量”)。
蘋果這樣做的目的是為了保護(hù)class的在編譯時(shí)期確定的內(nèi)存空間的連續(xù)性,防止runtime時(shí)期增加的變量或者方法造成的內(nèi)存重疊。
繼續(xù)objc_msgSend的調(diào)用過程,通過isa指針得到的method_list_t等信息,我們就直接可以得到對(duì)應(yīng)的IMP,然后調(diào)用函數(shù),同時(shí)存入cache表中。
這一切都是基于函數(shù)能夠成功調(diào)用的前提。那么,如果IMP沒有找到呢?runtime會(huì)被觸發(fā)另一套機(jī)制——消息轉(zhuǎn)發(fā)。
關(guān)于runtime 方法調(diào)用源碼中還有好多細(xì)節(jié),由于精力能力有限,以后會(huì)慢慢補(bǔ)充!