Objc源碼之對(duì)象創(chuàng)建alloc和init
Objc源碼之initialize實(shí)現(xiàn)
Objc源碼之Load方法實(shí)現(xiàn)
Objc源碼之NSObject和isa
Objc源碼之引用計(jì)數(shù)實(shí)現(xiàn)
objc源碼之Method消息發(fā)送
前言
?? 在我們進(jìn)行方法調(diào)用的時(shí)候,我們的對(duì)象是如何找到我們的方法呢恃逻?這個(gè)問題大家基本都知道是通過isa找到的溺蕉,實(shí)例對(duì)象通過isa找到類對(duì)象屿良,在類對(duì)象中查找方法朽砰,類對(duì)象通過isa指針找到元類,在元類對(duì)象中查找,那么在這個(gè)過程中究竟查找過程是怎么實(shí)現(xiàn)的,除了查找方法勾习,還會(huì)進(jìn)行哪些操作呢?這篇文章我們通過objc的源碼來看下具體的查找過程懈玻。
一巧婶、方法調(diào)用過程
TestObject *obj = [TestObject new];
[obj test];
我們以實(shí)例對(duì)象的方法調(diào)用為例,來說明一下方法的調(diào)用過程:
1.首先[obj test]會(huì)轉(zhuǎn)換成objc_msgSend(self,@ selector(test))函數(shù)調(diào)用。
2.obj通過isa指針找到類對(duì)象粹舵,實(shí)例對(duì)象的方法列表存在于類對(duì)象中钮孵。
3.類對(duì)象是一個(gè)objc_class結(jié)構(gòu)體,objc_class結(jié)構(gòu)中存在一個(gè)cache_t類型的cache眼滤,從cache里面的bucket_t中通過@ selector(test)為key來查找方法實(shí)現(xiàn)IMP巴席。
4.如果objc_class的cache中沒有查找到,就通過class_data_bits_t來獲取class_rw_t來獲取中的methods方法列表來查找test方法诅需。
5.如果類對(duì)象中沒有查找到對(duì)應(yīng)的方法漾唉,就通過objc_class結(jié)構(gòu)體中的superclass來找到對(duì)象的父類對(duì)象,然后重復(fù)3堰塌、4赵刑、5這個(gè)過程,如果還沒有查找到场刑,就會(huì)到到根類NSObject般此,NSObject的父對(duì)象是nil的(參考下面經(jīng)典的類關(guān)系圖),這個(gè)時(shí)候如果還沒有查找到牵现,就開始進(jìn)入消息轉(zhuǎn)發(fā)了铐懊。
6.進(jìn)入消息轉(zhuǎn)發(fā)階段以后。
- 首先是調(diào)用resolveInstanceMethod:或者resolveClassMethod:瞎疼,這一步可以給當(dāng)前類添加方法科乎,來響應(yīng)這個(gè)過程。
- 調(diào)用forwardingTargetForSelector:贼急,這一步是尋找一個(gè)備援接受者來響應(yīng)這個(gè)而方法茅茂。
-
調(diào)用methodSignatureForSelector和forwardInvocation,完整的消息轉(zhuǎn)發(fā)太抓,通過NSInvocation來響應(yīng)這個(gè)方法空闲。
7.如果上述過程都沒有響應(yīng),那么則會(huì)crash腻异,報(bào)unrecognized selector sent to instance的錯(cuò)誤进副。
二、objc_msgSend
?? 當(dāng)編譯器遇到一個(gè)方法調(diào)用時(shí)悔常,它會(huì)將方法的調(diào)用翻譯成以下函數(shù)中的一個(gè) objc_msgSend影斑、objc_msgSend_stret、objc_msgSendSuper 和 objc_msgSendSuper_stret机打。發(fā)送給對(duì)象的父類的消息會(huì)使用 objc_msgSendSuper 有數(shù)據(jù)結(jié)構(gòu)作為返回值的方法會(huì)使用 objc_msgSendSuper_stret 或 objc_msgSend_stret 其它的消息都是使用 objc_msgSend 發(fā)送的矫户。
?? 在objc_msgSend是OC實(shí)例對(duì)象和類對(duì)象發(fā)送消息的核心引擎,用來查找方法實(shí)現(xiàn)残邀,對(duì)性能要求較高皆辽,因此這一部分是通過匯編代碼來編寫的柑蛇。下面是歐陽(yáng)大哥通過匯編代碼,翻譯的c代碼深入解構(gòu)objc_msgSend函數(shù)的實(shí)現(xiàn)
//下面的結(jié)構(gòu)體中只列出objc_msgSend函數(shù)內(nèi)部訪問用到的那些數(shù)據(jù)結(jié)構(gòu)和成員驱闷。
/*
其實(shí)SEL類型就是一個(gè)字符串指針類型耻台,所描述的就是方法字符串指針
*/
typedef char * SEL;
/*
IMP類型就是所有OC方法的函數(shù)原型類型。
*/
typedef id (*IMP)(id self, SEL _cmd, ...);
/*
方法名和方法實(shí)現(xiàn)桶結(jié)構(gòu)體
*/
struct bucket_t {
SEL key; //方法名稱
IMP imp; //方法的實(shí)現(xiàn)空另,imp是一個(gè)函數(shù)指針類型
};
/*
用于加快方法執(zhí)行的緩存結(jié)構(gòu)體盆耽。這個(gè)結(jié)構(gòu)體其實(shí)就是一個(gè)基于開地址沖突解決法的哈希桶。
*/
struct cache_t {
struct bucket_t *buckets; //緩存方法的哈希桶數(shù)組指針扼菠,桶的數(shù)量 = mask + 1
int mask; //桶的數(shù)量 - 1
int occupied; //桶中已經(jīng)緩存的方法數(shù)量摄杂。
};
/*
OC對(duì)象的類結(jié)構(gòu)體描述表示,所有OC對(duì)象的第一個(gè)參數(shù)保存是的一個(gè)isa指針循榆。
*/
struct objc_object {
void *isa;
};
/*
OC類信息結(jié)構(gòu)體析恢,這里只展示出了必要的數(shù)據(jù)成員。
*/
struct objc_class : objc_object {
struct objc_class * superclass; //基類信息結(jié)構(gòu)體秧饮。
cache_t cache; //方法緩存哈希表
//... 其他數(shù)據(jù)成員忽略映挂。
};
/*
objc_msgSend的C語(yǔ)言版本偽代碼實(shí)現(xiàn).
receiver: 是調(diào)用方法的對(duì)象
op: 是要調(diào)用的方法名稱字符串
*/
id objc_msgSend(id receiver, SEL op, ...)
{
//1............................ 對(duì)象空值判斷。
//如果傳入的對(duì)象是nil則直接返回nil
if (receiver == nil)
return nil;
//2............................ 獲取或者構(gòu)造對(duì)象的isa數(shù)據(jù)盗尸。
void *isa = NULL;
//如果對(duì)象的地址最高位為0則表明是普通的OC對(duì)象袖肥,否則就是Tagged Pointer類型的對(duì)象
if ((receiver & 0x8000000000000000) == 0) {
struct objc_object *ocobj = (struct objc_object*) receiver;
isa = ocobj->isa;
}
else { //Tagged Pointer類型的對(duì)象中沒有直接保存isa數(shù)據(jù),所以需要特殊處理來查找對(duì)應(yīng)的isa數(shù)據(jù)振劳。
//如果對(duì)象地址的最高4位為0xF, 那么表示是一個(gè)用戶自定義擴(kuò)展的Tagged Pointer類型對(duì)象
if (((NSUInteger) receiver) >= 0xf000000000000000) {
//自定義擴(kuò)展的Tagged Pointer類型對(duì)象中的52-59位保存的是一個(gè)全局?jǐn)U展Tagged Pointer類數(shù)組的索引值。
int classidx = (receiver & 0xFF0000000000000) >> 52
isa = objc_debug_taggedpointer_ext_classes[classidx];
}
else {
//系統(tǒng)自帶的Tagged Pointer類型對(duì)象中的60-63位保存的是一個(gè)全局Tagged Pointer類數(shù)組的索引值油狂。
int classidx = ((NSUInteger) receiver) >> 60;
isa = objc_debug_taggedpointer_classes[classidx];
}
}
//因?yàn)閮?nèi)存地址對(duì)齊的原因和虛擬內(nèi)存空間的約束原因历恐,
//以及isa定義的原因需要將isa與上0xffffffff8才能得到對(duì)象所屬的Class對(duì)象。
struct objc_class *cls = (struct objc_class *)(isa & 0xffffffff8);
//3............................ 遍歷緩存哈希桶并查找緩存中的方法實(shí)現(xiàn)专筷。
IMP imp = NULL;
//cmd與cache中的mask進(jìn)行與計(jì)算得到哈希桶中的索引弱贼,來查找方法是否已經(jīng)放入緩存cache哈希桶中。
int index = cls->cache.mask & op;
while (true) {
//如果緩存哈希桶中命中了對(duì)應(yīng)的方法實(shí)現(xiàn)磷蛹,則保存到imp中并退出循環(huán)吮旅。
if (cls->cache.buckets[index].key == op) {
imp = cls->cache.buckets[index].imp;
break;
}
//方法實(shí)現(xiàn)并沒有被緩存,并且對(duì)應(yīng)的桶的數(shù)據(jù)是空的就退出循環(huán)
if (cls->cache.buckets[index].key == NULL) {
break;
}
//如果哈希桶中對(duì)應(yīng)的項(xiàng)已經(jīng)被占用但是又不是要執(zhí)行的方法味咳,則通過開地址法來繼續(xù)尋找緩存該方法的桶庇勃。
if (index == 0) {
index = cls->cache.mask; //從尾部尋找
}
else {
index--; //索引減1繼續(xù)尋找。
}
} /*end while*/
//4............................ 執(zhí)行方法實(shí)現(xiàn)或方法未命中緩存處理函數(shù)
if (imp != NULL)
return imp(receiver, op, ...); //這里的... 是指?jìng)鬟f給objc_msgSend的OC方法中的參數(shù)槽驶。
else
return objc_msgSend_uncached(receiver, op, cls, ...);
}
/*
方法未命中緩存處理函數(shù):objc_msgSend_uncached的C語(yǔ)言版本偽代碼實(shí)現(xiàn)责嚷,這個(gè)函數(shù)也是用匯編語(yǔ)言編寫。
*/
id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls)
{
//這個(gè)函數(shù)很簡(jiǎn)單就是直接調(diào)用了_class_lookupMethodAndLoadCache3 來查找方法并緩存到struct objc_class中的cache中掂铐,最后再返回IMP類型罕拂。
IMP imp = _class_lookupMethodAndLoadCache3(receiver, op, cls);
return imp(receiver, op, ....);
}
上面的代碼揍异,總結(jié)一下:
1.對(duì)象空值判斷,這個(gè)就是在OC中為什么給空對(duì)象發(fā)送消息爆班,不crash的原因衷掷。
2. 獲取或者構(gòu)造對(duì)象的isa數(shù)據(jù),通過isa查找類或者元類
3. 遍歷緩存哈希桶并查找緩存中的方法實(shí)現(xiàn)柿菩,通過cache查找是否命中緩存
4. 執(zhí)行方法實(shí)現(xiàn)或方法未命中緩存處理函數(shù)objc_msgSend_uncached
三戚嗅、lookUpImpOrForward
lookUpImpOrForward是方法調(diào)用過程的核心類,方法的查找碗旅、類的初始化渡处、initialize都可能在這里面調(diào)用。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
//1. 緩存查找
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
//2. 類是否實(shí)現(xiàn)
if (!cls->isRealized()) {
realizeClass(cls);
}
//3. 類是否初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
runtimeLock.assertLocked();
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 4.方法列表查找,查找到以后暑劝,進(jìn)行緩存竭贩。
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 5.父類方法列表查找,查找到進(jìn)行緩存醇份。
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 6.如果還沒有查找到。進(jìn)入消息轉(zhuǎn)發(fā)resolveMethod方法
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
lookUpImpOrForward方法有如下過程:
1. 緩存中查找方法
2. 類是否實(shí)現(xiàn)
3.類是否初始化
4.方法列表查找吼具,查找到以后僚纷,進(jìn)行緩存。
5.父類方法列表查找拗盒,查找到進(jìn)行緩存怖竭。
6.如果還沒有查找到。進(jìn)入消息轉(zhuǎn)發(fā)resolveMethod方法
這里的方法查找過程陡蝇,我在第一部分的方法調(diào)用過程中都有描述過痊臭,我重點(diǎn)說一下2和3,這兩部分是做什么登夫。
- 類是否實(shí)現(xiàn)广匙,這一部分主要是判斷類是否是第一次調(diào)用,第一次調(diào)用的時(shí)候恼策,class_rw_t可能還沒有創(chuàng)建好鸦致,因?yàn)榉椒ㄊ谴嬖谶@里面的,所以要保證類已經(jīng)實(shí)現(xiàn)涣楷。
- 類是否初始化分唾,這一部分主要是初始化類的一些參數(shù),包括isa指針狮斗,同時(shí)我們熟悉的Initialize方法也是在這里調(diào)用的鳍寂。
四、消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)是在運(yùn)行時(shí)進(jìn)行的情龄,大致分為三個(gè)階段:
第一階段是先檢查接收者迄汛,看是否能通過runtime動(dòng)態(tài)添加一個(gè)方法捍壤,來處理這個(gè)方法;
第二階段就是備援接收者鞍爱,看看有沒有對(duì)象可以響應(yīng)這個(gè)方法鹃觉。
第二階段就是把該消息的全部信息封裝到NSInvocation對(duì)象中,看哪個(gè)對(duì)象能否處理睹逃,如果還無法處理盗扇,則報(bào)錯(cuò)unrecognized selector sent to instance。
1.動(dòng)態(tài)方法解析
// 類方法專用
+ (BOOL)resolveClassMethod:(SEL)sel
// 對(duì)象方法專用
+ (BOOL)resolveInstanceMethod:(SEL)sel
2.備援接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
3.完整消息轉(zhuǎn)發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
在方法簽名的過程中沉填,注意簽名符號(hào):
* 代表 char *
char BOOL 代表 c
: 代表 SEL
^type 代表 type *
@ 代表 NSObject * 或 id
^@ 代表 NSError **
# 代表 NSObject
v 代表 void
五疗隶、總結(jié)
方法的調(diào)用過程:
1.緩存查找
2.查找當(dāng)前類的緩存及方法。
3.查找父類的緩存及方法
4.消息轉(zhuǎn)發(fā)
參考:
objc4-750源碼
從源代碼看 ObjC 中消息的發(fā)送.md
深入解構(gòu)objc_msgSend函數(shù)的實(shí)現(xiàn)
iOS消息轉(zhuǎn)發(fā)機(jī)制實(shí)例
iOS的消息轉(zhuǎn)發(fā)機(jī)制詳解