objc_msgSend 是 OC 编译成 C 语言过程时调用的,也是调用最频繁的一个语句。这里我们分析下,苹果是怎么调用 objc_msgSend 以及调用之后的操作是怎样的。
在 这里 我们可以下载到苹果公开的部分源码。
1.objc_msgSend 的调用
objc_msgSend是用汇编语言实现的,所以针对不同的硬件环境采用了不同的文件,这里我们只分析arm环境下的源码。
/********************************************************************
*
* 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 通过isa指针回去类对象
CacheLookup NORMAL //cache内查找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 //调用_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 调用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
以上汇编大家看起来费劲。这里转成 C 语言的伪代码:
/*
objc_msgSend的C语言版本伪代码实现.
receiver: 是调用方法的对象
op: 是要调用的方法名称字符串
*/
id objc_msgSend(id receiver, SEL op, ...)
{
//1............................ 对象空值判断。
//如果传入的对象是nil则直接返回nil
if (receiver == nil)
return nil;
//2............................ 获取或者构造对象的isa数据。
void *isa = NULL;
//如果对象的地址最高位为0则表明是普通的OC对象,否则就是Tagged Pointer类型的对象
if ((receiver & 0x8000000000000000) == 0) {
struct objc_object *ocobj = (struct objc_object*) receiver;
isa = ocobj->isa;
}
else { //Tagged Pointer类型的对象中没有直接保存isa数据,所以需要特殊处理来查找对应的isa数据。
//如果对象地址的最高4位为0xF, 那么表示是一个用户自定义扩展的Tagged Pointer类型对象
if (((NSUInteger) receiver) >= 0xf000000000000000) {
//自定义扩展的Tagged Pointer类型对象中的52-59位保存的是一个全局扩展Tagged Pointer类数组的索引值。
int classidx = (receiver & 0xFF0000000000000) >> 52
isa = objc_debug_taggedpointer_ext_classes[classidx];
}
else {
//系统自带的Tagged Pointer类型对象中的60-63位保存的是一个全局Tagged Pointer类数组的索引值。
int classidx = ((NSUInteger) receiver) >> 60;
isa = objc_debug_taggedpointer_classes[classidx];
}
}
//因为内存地址对齐的原因和虚拟内存空间的约束原因,
//以及isa定义的原因需要将isa与上0xffffffff8才能得到对象所属的Class对象。
struct objc_class *cls = (struct objc_class *)(isa & 0xffffffff8);
//3............................ 遍历缓存哈希桶并查找缓存中的方法实现。
IMP imp = NULL;
//cmd与cache中的mask进行与计算得到哈希桶中的索引,来查找方法是否已经放入缓存cache哈希桶中。
int index = cls->cache.mask & op;
while (true) {
//如果缓存哈希桶中命中了对应的方法实现,则保存到imp中并退出循环。
if (cls->cache.buckets[index].key == op) {
imp = cls->cache.buckets[index].imp;
break;
}
//方法实现并没有被缓存,并且对应的桶的数据是空的就退出循环
if (cls->cache.buckets[index].key == NULL) {
break;
}
//如果哈希桶中对应的项已经被占用但是又不是要执行的方法,则通过开地址法来继续寻找缓存该方法的桶。
if (index == 0) {
index = cls->cache.mask; //从尾部寻找
}
else {
index--; //索引减1继续寻找。
}
} /*end while*/
//4............................ 执行方法实现或方法未命中缓存处理函数
if (imp != NULL)
return imp(receiver, op, ...); //这里的... 是指传递给objc_msgSend的OC方法中的参数。
else
return objc_msgSend_uncached(receiver, op, cls, ...);
}
/*
方法未命中缓存处理函数:objc_msgSend_uncached的C语言版本伪代码实现,这个函数也是用汇编语言编写。
*/
id objc_msgSend_uncached(id receiver, SEL op, struct objc_class *cls)
{
//这个函数很简单就是直接调用了_class_lookupMethodAndLoadCache3 来查找方法并缓存到struct objc_class中的cache中,最后再返回IMP类型。
IMP imp = _class_lookupMethodAndLoadCache3(receiver, op, cls);
return imp(receiver, op, ....);
}
通过以上分析。我们发现还要调用 __class_lookupMethodAndLoadCache3 方法。
继续分析__class_lookupMethodAndLoadCache3内做了些什么操作。
最总我们发现这个方法是在 objc-runtime.mm 内实现的。
实现代码如下:
/***********************************************************************
* _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查找 汇编实现
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);//校验是否存在这个类
if (!cls->isRealized()) { //2........类对象是否初始化
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内是否查到?查到返回IMP
if (imp) goto done;
// Try this class's method lists. 3.2........... 类对象内查找
{
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 ............父类对象cache和method list内查找
{
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........动态加载 做标记 只加载一次
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;
}
通过以上分析 发现一个问题,这里只有 resolveMethod 没有消息转发呀!莫非我哪里漏掉了什么?再想想看