objc_msgSend 是 OC 編譯成 C 語(yǔ)言過(guò)程時(shí)調(diào)用的,也是調(diào)用最頻繁的一個(gè)語(yǔ)句画饥。這里我們分析下,蘋(píng)果是怎么調(diào)用 objc_msgSend 以及調(diào)用之后的操作是怎樣的浊猾。
在 這里 我們可以下載到蘋(píng)果公開(kāi)的部分源碼荒澡。
1.objc_msgSend 的調(diào)用
objc_msgSend是用匯編語(yǔ)言實(shí)現(xiàn)的,所以針對(duì)不同的硬件環(huán)境采用了不同的文件与殃,這里我們只分析arm環(huán)境下的源碼单山。
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in r12
* Forwarding returned in Z flag
* r9 reserved for our use but not used
*
********************************************************************/
ENTRY _objc_msgSend
cbz r0, LNilReceiver_f //判斷Receiver是否存在
ldr r9, [r0] // r9 = self->isa
GetClassFromIsa // r9 = class 通過(guò)isa指針回去類(lèi)對(duì)象
CacheLookup NORMAL //cache內(nèi)查找IMP是否存在
// cache hit, IMP in r12, eq already set for nonstret forwarding
bx r12 // call imp
CacheLookup2 NORMAL
// cache miss
ldr r9, [r0] // r9 = self->isa
GetClassFromIsa // r9 = class
b __objc_msgSend_uncached //調(diào)用_objc_msgSend_uncached
LNilReceiver:
// r0 is already zero
mov r1, #0
mov r2, #0
mov r3, #0
FP_RETURN_ZERO
bx lr
END_ENTRY _objc_msgSend
/////////////////////////////////////////////////////////////////////
//
// CacheLookup NORMAL|STRET
// CacheLookup2 NORMAL|STRET
//
// Locate the implementation for a selector in a class's method cache.
//
// Takes:
// $0 = NORMAL, STRET
// r0 or r1 (STRET) = receiver
// r1 or r2 (STRET) = selector
// r9 = class to search in
//
// On exit: r9 clobbered
// (found) continues after CacheLookup, IMP in r12, eq set
// (not found) continues after CacheLookup2
//
/////////////////////////////////////////////////////////////////////
.macro CacheLookup
ldrh r12, [r9, #CACHE_MASK] // r12 = mask
ldr r9, [r9, #CACHE] // r9 = buckets
.if $0 == STRET
and r12, r12, r2 // r12 = index = SEL & mask
.else
and r12, r12, r1 // r12 = index = SEL & mask
.endif
add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
ldr r12, [r9, #CACHED_SEL] // r12 = bucket->sel
6:
.if $0 == STRET
teq r12, r2
.else
teq r12, r1
.endif
bne 8f
ldr r12, [r9, #CACHED_IMP] // r12 = bucket->imp
.if $0 == STRET
tst r12, r12 // set ne for stret forwarding
.else
// eq already set for nonstret forwarding by `teq` above
.endif
.endmacro
.macro CacheLookup2
#if CACHED_SEL != 0
# error this code requires that SEL be at offset 0
#endif
8:
cmp r12, #1
blo 8f // if (bucket->sel == 0) cache miss
it eq // if (bucket->sel == 1) cache wrap
ldreq r9, [r9, #CACHED_IMP] // bucket->imp is before first bucket
ldr r12, [r9, #8]! // r12 = (++bucket)->sel
b 6b
8:
.endmacro
/////////////////////////////////////////////////////////////////////
//
// GetClassFromIsa return-type
//
// Given an Isa, return the class for the Isa.
//
// Takes:
// r9 = class
//
// On exit: r12 clobbered
// r9 contains the class for this Isa.
//
/////////////////////////////////////////////////////////////////////
.macro GetClassFromIsa
#if SUPPORT_INDEXED_ISA
// Note: We are doing a little wasted work here to load values we might not
// need. Branching turns out to be even worse when performance was measured.
MI_GET_ADDRESS(r12, _objc_indexed_classes)
tst.w r9, #ISA_INDEX_IS_NPI_MASK
itt ne
ubfxne r9, r9, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS
ldrne.w r9, [r12, r9, lsl #2]
#endif
.endmacro
/********************************************************************
* IMP cache_getImp(Class cls, SEL sel)
*
* On entry: r0 = class whose cache is to be searched
* r1 = selector to search for
*
* If found, returns method implementation.
* If not found, returns NULL.
********************************************************************/
STATIC_ENTRY _cache_getImp
mov r9, r0
CacheLookup NORMAL
// cache hit, IMP in r12
mov r0, r12
bx lr // return imp
CacheLookup2 GETIMP
// cache miss, return nil
mov r0, #0
bx lr
END_ENTRY _cache_getImp
STATIC_ENTRY __objc_msgSend_uncached
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r9 is the class to search
MethodTableLookup NORMAL // returns IMP in r12 調(diào)用MethodTableLookup
bx r12
END_ENTRY __objc_msgSend_uncached
/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup NORMAL|STRET
//
// Locate the implementation for a selector in a class's method lists.
//
// Takes:
// $0 = NORMAL, STRET
// r0 or r1 (STRET) = receiver
// r1 or r2 (STRET) = selector
// r9 = class to search in
//
// On exit: IMP in r12, eq/ne set for forwarding
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup
stmfd sp!, {r0-r3,r7,lr}
add r7, sp, #16
sub sp, #8 // align stack
FP_SAVE
.if $0 == NORMAL
// receiver already in r0
// selector already in r1
.else
mov r0, r1 // receiver
mov r1, r2 // selector
.endif
mov r2, r9 // class to search
blx __class_lookupMethodAndLoadCache3
mov r12, r0 // r12 = IMP
.if $0 == NORMAL
cmp r12, r12 // set eq for nonstret forwarding
.else
tst r12, r12 // set ne for stret forwarding
.endif
FP_RESTORE
add sp, #8 // align stack
ldmfd sp!, {r0-r3,r7,lr}
.endmacro
以上匯編大家看起來(lái)費(fèi)勁。這里轉(zhuǎn)成 C 語(yǔ)言的偽代碼:
/*
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類(lèi)型的對(duì)象
if ((receiver & 0x8000000000000000) == 0) {
struct objc_object *ocobj = (struct objc_object*) receiver;
isa = ocobj->isa;
}
else { //Tagged Pointer類(lèi)型的對(duì)象中沒(méi)有直接保存isa數(shù)據(jù)爽篷,所以需要特殊處理來(lái)查找對(duì)應(yīng)的isa數(shù)據(jù)悴晰。
//如果對(duì)象地址的最高4位為0xF, 那么表示是一個(gè)用戶自定義擴(kuò)展的Tagged Pointer類(lèi)型對(duì)象
if (((NSUInteger) receiver) >= 0xf000000000000000) {
//自定義擴(kuò)展的Tagged Pointer類(lèi)型對(duì)象中的52-59位保存的是一個(gè)全局?jǐn)U展Tagged Pointer類(lèi)數(shù)組的索引值。
int classidx = (receiver & 0xFF0000000000000) >> 52
isa = objc_debug_taggedpointer_ext_classes[classidx];
}
else {
//系統(tǒng)自帶的Tagged Pointer類(lèi)型對(duì)象中的60-63位保存的是一個(gè)全局Tagged Pointer類(lèi)數(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ì)算得到哈希桶中的索引棕硫,來(lái)查找方法是否已經(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)并沒(méi)有被緩存,并且對(duì)應(yīng)的桶的數(shù)據(jù)是空的就退出循環(huán)
if (cls->cache.buckets[index].key == NULL) {
break;
}
//如果哈希桶中對(duì)應(yīng)的項(xiàng)已經(jīng)被占用但是又不是要執(zhí)行的方法蚓再,則通過(guò)開(kāi)地址法來(lái)繼續(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ǔ)言編寫(xiě)。
*/
id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls)
{
//這個(gè)函數(shù)很簡(jiǎn)單就是直接調(diào)用了_class_lookupMethodAndLoadCache3 來(lái)查找方法并緩存到struct objc_class中的cache中娃属,最后再返回IMP類(lèi)型六荒。
IMP imp = _class_lookupMethodAndLoadCache3(receiver, op, cls);
return imp(receiver, op, ....);
}
通過(guò)以上分析。我們發(fā)現(xiàn)還要調(diào)用 __class_lookupMethodAndLoadCache3 方法膳犹。
繼續(xù)分析__class_lookupMethodAndLoadCache3內(nèi)做了些什么操作恬吕。
最總我們發(fā)現(xiàn)這個(gè)方法是在 objc-runtime.mm 內(nèi)實(shí)現(xiàn)的。
實(shí)現(xiàn)代碼如下:
/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel); //1........ cache查找 匯編實(shí)現(xiàn)
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();//加鎖
checkIsKnownClass(cls);//校驗(yàn)是否存在這個(gè)類(lèi)
if (!cls->isRealized()) { //2........類(lèi)對(duì)象是否初始化
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel); //3.1........cache內(nèi)是否查到须床?查到返回IMP
if (imp) goto done;
// Try this class's method lists. 3.2........... 類(lèi)對(duì)象內(nèi)查找
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists. 3.3 ............父類(lèi)對(duì)象cache和method list內(nèi)查找
{
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 {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
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;
}
}
}
// No implementation found. Try method resolver once.
// 4........動(dòng)態(tài)加載 做標(biāo)記 只加載一次
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; //重新加載一遍
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
通過(guò)以上分析 發(fā)現(xiàn)一個(gè)問(wèn)題铐料,這里只有 resolveMethod 沒(méi)有消息轉(zhuǎn)發(fā)呀!莫非我哪里漏掉了什么豺旬?再想想看