objc_msgSend 函数简介
在 Objective-C 中,所有的消息传递中的“消息”都会被编译器转化为:
id objc_msgSend ( id self, SEL op, ... );
/**
* Sends a message with a simple return value to an instance of a class.
*
* @param self A pointer to the instance of the class that is to receive the message.
* @param op The selector of the method that handles the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method.
*
* @note When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
*/
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
注释中大概的意思是当编译器遇到一个方法调用时,它会将方法的调用翻译成以下函数中的一个 objc_msgSend
、objc_msgSend_stret
、objc_msgSendSuper
和 objc_msgSendSuper_stret
;
发送给对象的父类的消息会使用 objc_msgSendSuper
;
有数据结构作为返回值的方法会使用 objc_msgSendSuper_stret
或 objc_msgSend_stret
;
其它的消息都是使用 objc_msgSend
发送的;
消息发送步骤
消息发送的主要步骤:
- 检查
selector
是否需要忽略。 - 检查
target
是否为nil
。如果为nil
,直接cleanup
,然后return
。这一点就是为何在OC中给nil发送消息不会崩溃的原因; - 确定不是给
nil
发消息之后,就开始查找这个类对应的IMP
实现;
查找 IMP
的过程:
- 先从当前
class
的cache
方法列表里去查找。 - 如果找到了,如果找到了就返回对应的
IMP
实现,并把当前的class
中的selector
缓存到cache
里面。 - 如果类的方法列表中找不到就到父类的方法列表中查找,一直找到
NSObject
类为止。 - 最后再找不到,就会进入动态方法解析和消息转发的机制。
objc_msgSend 源码解析
以 x86_64 为例,删除其他的代码
//方法发送
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in r11
* Forwarding returned in Z flag
* r10 reserved for our use but not used
*
********************************************************************/
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
//1.判断空处理
NilTest NORMAL
//2.获取isa
GetIsaFast NORMAL // r10 = self->isa
//3.缓存中查找
CacheLookup NORMAL, CALL // calls IMP on success
NilTestReturnZero NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
MESSENGER_END_SLOW
//4.查找方法
jmp __objc_msgSend_uncached
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
NilTest NORMAL
GetIsaFast NORMAL // r10 = self->isa
CacheLookup NORMAL, LOOKUP // returns IMP on success
NilTestReturnIMP NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
jmp __objc_msgLookup_uncached
END_ENTRY _objc_msgLookup
ENTRY _objc_msgSend_fixup
int3
END_ENTRY _objc_msgSend_fixup
STATIC_ENTRY _objc_msgSend_fixedup
// Load _cmd from the message_ref
movq 8(%a2), %a2
jmp _objc_msgSend
END_ENTRY _objc_msgSend_fixedup
从上面汇编代码中看出,objc_msgSend
,会分为有缓存和无缓存两种情况的处理。
/////////////////////////////////////////////////////////////////////
//
// NilTest return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// %a1 or %a2 (STRET) = receiver
//
// On exit: Loads non-nil receiver in %a1 or %a2 (STRET)
// or returns.
//
// NilTestReturnZero return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// %a1 or %a2 (STRET) = receiver
//
// On exit: Loads non-nil receiver in %a1 or %a2 (STRET)
// or returns zero.
//
// NilTestReturnIMP return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// %a1 or %a2 (STRET) = receiver
//
// On exit: Loads non-nil receiver in %a1 or %a2 (STRET)
// or returns an IMP in r11 that returns zero.
//
/////////////////////////////////////////////////////////////////////
.macro ZeroReturn
xorl %eax, %eax
xorl %edx, %edx
xorps %xmm0, %xmm0
xorps %xmm1, %xmm1
.endmacro
.macro ZeroReturnFPRET
fldz
ZeroReturn
.endmacro
.macro ZeroReturnFP2RET
fldz
fldz
ZeroReturn
.endmacro
.macro ZeroReturnSTRET
// rax gets the struct-return address as passed in rdi
movq %rdi, %rax
.endmacro
STATIC_ENTRY __objc_msgNil
ZeroReturn
ret
END_ENTRY __objc_msgNil
STATIC_ENTRY __objc_msgNil_fpret
ZeroReturnFPRET
ret
END_ENTRY __objc_msgNil_fpret
STATIC_ENTRY __objc_msgNil_fp2ret
ZeroReturnFP2RET
ret
END_ENTRY __objc_msgNil_fp2ret
STATIC_ENTRY __objc_msgNil_stret
ZeroReturnSTRET
ret
END_ENTRY __objc_msgNil_stret
.macro NilTest
.if $0 != STRET
testq %a1, %a1
.else
testq %a2, %a2
.endif
PN
jz LNilTestSlow_f
.endmacro
.macro NilTestReturnZero
.align 3
LNilTestSlow:
.if $0 == NORMAL
ZeroReturn
.elseif $0 == FPRET
ZeroReturnFPRET
.elseif $0 == FP2RET
ZeroReturnFP2RET
.elseif $0 == STRET
ZeroReturnSTRET
.else
.abort oops
.endif
MESSENGER_END_NIL
ret
.endmacro
.macro NilTestReturnIMP
.align 3
LNilTestSlow:
.if $0 == NORMAL
leaq __objc_msgNil(%rip), %r11
.elseif $0 == FPRET
leaq __objc_msgNil_fpret(%rip), %r11
.elseif $0 == FP2RET
leaq __objc_msgNil_fp2ret(%rip), %r11
.elseif $0 == STRET
leaq __objc_msgNil_stret(%rip), %r11
.else
.abort oops
.endif
ret
.endmacro
NilTest
是用来检测是否为 nil
的。传入参数有 4
种,NORMAL
、FPRET
、FP2RET
和 STRET
。
objc_msgSend
传入的参数是 NilTest NORMAL
;
objc_msgSend_fpret
传入的参数是 NilTest FPRET
;
objc_msgSend_fp2ret
传入的参数是 NilTest FP2RET
;
objc_msgSend_stret
传入的参数是 NilTest STRET
;
如果检测方法的接受者是 nil
,那么系统会自动 clean
并且 return
。
/////////////////////////////////////////////////////////////////////
//
// GetIsaFast return-type
// GetIsaSupport return-type
//
// Sets r10 = obj->isa. Consults the tagged isa table if necessary.
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
// a1 or a2 (STRET) = receiver
//
// On exit: r10 = receiver->isa
// r11 is clobbered
//
/////////////////////////////////////////////////////////////////////
.macro GetIsaFast
.if $0 != STRET
testb $$1, %a1b
PN
jnz LGetIsaSlow_f
movq $$0x00007ffffffffff8, %r10
andq (%a1), %r10
.else
testb $$1, %a2b
PN
jnz LGetIsaSlow_f
movq $$0x00007ffffffffff8, %r10
andq (%a2), %r10
.endif
LGetIsaDone:
.endmacro
GetIsaFast
宏可以快速地获取到对象的 isa
指针地址。r10 = obj->isa
无缓存
通过 objc_msgSend
汇编代码中可以看出,如果没有命中缓存,会搜索方法列表,程序跳到 __objc_msgSend_uncached
,就说明 cache
中无缓存,未命中缓存。
/********************************************************************
*
* _objc_msgSend_uncached
* _objc_msgSend_stret_uncached
* _objc_msgLookup_uncached
* _objc_msgLookup_stret_uncached
*
* The uncached method lookup.
*
********************************************************************/
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup NORMAL // r11 = IMP
jmp *%r11 // goto *imp
END_ENTRY __objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_stret_uncached
UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup STRET // r11 = IMP
jmp *%r11 // goto *imp
END_ENTRY __objc_msgSend_stret_uncached
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
// 方法列表中查找
MethodTableLookup NORMAL // r11 = IMP
ret
END_ENTRY __objc_msgLookup_uncached
STATIC_ENTRY __objc_msgLookup_stret_uncached
UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class
// r10 is already the class to search
MethodTableLookup STRET // r11 = IMP
ret
END_ENTRY __objc_msgLookup_stret_uncached
查看 __objc_msgSend_uncached
汇编代码,会直接调用 MethodTableLookup
中查找方法列表。
/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup NORMAL|STRET
//
// Takes: a1 or a2 (STRET) = receiver
// a2 or a3 (STRET) = selector to search for
// r10 = class to search
//
// On exit: imp in %r11, eq/ne set for forwarding
//
/////////////////////////////////////////////////////////////////////
//汇编宏定义 MethodTableLookup
.macro MethodTableLookup
push %rbp
mov %rsp, %rbp
sub $$0x80+8, %rsp // +8 for alignment
movdqa %xmm0, -0x80(%rbp)
push %rax // might be xmm parameter count
movdqa %xmm1, -0x70(%rbp)
push %a1
movdqa %xmm2, -0x60(%rbp)
push %a2
movdqa %xmm3, -0x50(%rbp)
push %a3
movdqa %xmm4, -0x40(%rbp)
push %a4
movdqa %xmm5, -0x30(%rbp)
push %a5
movdqa %xmm6, -0x20(%rbp)
push %a6
movdqa %xmm7, -0x10(%rbp)
// _class_lookupMethodAndLoadCache3(receiver, selector, class)
.if $0 == NORMAL
// receiver already in a1
// selector already in a2
.else
movq %a2, %a1
movq %a3, %a2
.endif
movq %r10, %a3
//调用runtime中的__class_lookupMethodAndLoadCache3
call __class_lookupMethodAndLoadCache3
// IMP is now in %rax
movq %rax, %r11
movdqa -0x80(%rbp), %xmm0
pop %a6
movdqa -0x70(%rbp), %xmm1
pop %a5
movdqa -0x60(%rbp), %xmm2
pop %a4
movdqa -0x50(%rbp), %xmm3
pop %a3
movdqa -0x40(%rbp), %xmm4
pop %a2
movdqa -0x30(%rbp), %xmm5
pop %a1
movdqa -0x20(%rbp), %xmm6
pop %rax
movdqa -0x10(%rbp), %xmm7
.if $0 == NORMAL
cmp %r11, %r11 // set eq for nonstret forwarding
.else
test %r11, %r11 // set ne for stret forwarding
.endif
leave
.endmacro
MethodTableLookup
是汇编宏定义的一段代码,从中发现一个有用的信息 __class_lookupMethodAndLoadCache3
,这个函数在当前的汇编代码里面是找不到实现的。如果去 objc
源码进行全局搜索,也搜不到。如果是一个 C
函数,在底层汇编里面如果需要调用的话,苹果会为其加一个下划线 _
,因此上面的的函数删去一个下划线,_class_lookupMethodAndLoadCache3
;
/***********************************************************************
* _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
函数,下面具体分析一下这个函数;
/***********************************************************************
* 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();
//cache传入值为NO跳过此条件语句
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
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.read();
//1.类是否实现了
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
//类实现
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
//2.类是否初始化了
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
//---类初始化
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// 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.assertReading();
// Try this class's cache.
//3.查找缓存列表
imp = cache_getImp(cls, sel);
if (imp) goto done;
//4.当前类方法列表中查找
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
//5.添加到缓存列表中
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
//6.循环父类方法列表中查找
// Try superclass caches and method lists.
{
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.");
}
//(1)查找父类缓存列表
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
//需要判断缓存是否_objc_msgForward_impcache
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
//(2)把父类缓存中的方法添加到当前类缓存中
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;
}
}
//(3)查找父类方法列表
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//(4)把方法列表中的方法添加到当前类缓存中
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
//6.IMP没有找到,尝试方法解析一次
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// 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;
}
//7.IMP仍然没有找到,并且解析失败,则使用消息转发
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst); //当IMP为_objc_msgForward_impcache也添加到缓存中去了
done:
runtimeLock.unlockRead();
return imp;
}
无锁的缓存查找
runtimeLock.assertUnlocked();
//cache传入值为NO跳过此条件语句
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
在没有加锁的时候对缓存进行查找,提高缓存使用的性能,因为 _class_lookupMethodAndLoadCache3
传入的 cache = NO
,所以这里会直接跳过 if
中代码的执行,如果传入的是 YES
,那么就会调用 cache_getImp
方法去找到缓存里面对应的 IMP
。
//1.类是否实现了
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
//类实现
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
//2.类是否初始化了
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
//---类初始化
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// 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
}
在 Objective-C
运行时,初始化的过程中会对其中的类进行第一次初始化也就是执行 realizeClass
方法,为类分配可读写结构体 class_rw_t
的空间,并返回正确的类结构体。
加锁
runtimeLock.read();
因为在运行时中会动态的添加方法,为了保证线程安全,所以要加锁。
//4.当前类方法列表中查找
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
//5.添加到缓存列表中
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
从当前类的方法列表中寻找方法的实现,调用 getMethodNoSuper_nolock
方法查找对应的方法的结构体指针 method_t
。
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
在 getMethodNoSuper_nolock
方法中,会遍历一次 methodList
链表,从 beginLists
一直遍历到 endLists
。遍历过程中会调用 search_method_list
函数。
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
在 search_method_list
函数中,会去判断当前 methodList
是否有序,如果有序,会调用 findMethodInSortedMethodList
方法,这个方法里面的实现是一个二分搜索,如果非有序,就傻瓜式的遍历搜索。
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
如果在这里找到了方法的实现,将它加入类的缓存中。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
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.
}
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.
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
这段代码的分析在 类结构中 cache_t 如何缓存 sel 提到过。
//6.循环父类方法列表中查找
// Try superclass caches and method lists.
{
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.");
}
//(1)查找父类缓存列表
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
//需要判断缓存是否_objc_msgForward_impcache
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
//(2)把父类缓存中的方法添加到当前类缓存中
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;
}
}
//(3)查找父类方法列表
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//(4)把方法列表中的方法添加到当前类缓存中
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
如果在当前 class
对象里面没有找到该方法,那么会通过 class
对象的 superclass
指针查找父类的 class
的方法列表。同理,找到后把父类方法列表中的方法添加到当前类缓存中,而不是缓存到父类中。
有缓存
.macro CacheLookup
.if $0 != STRET
movq %a2, %r11 // r11 = _cmd
.else
movq %a3, %r11 // r11 = _cmd
.endif
andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask
shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4
addq 16(%r10), %r11 // r11 = class->cache.buckets + offset
.if $0 != STRET
cmpq (%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq (%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1f // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
1:
// loop
cmpq $$1, (%r11)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++
2:
.if $0 != STRET
cmpq (%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq (%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
3:
// wrap or miss
jb LCacheMiss_f // if (bucket->sel < 1) cache miss
// wrap
movq 8(%r11), %r11 // bucket->imp is really first bucket
jmp 2f
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
1:
// loop
cmpq $$1, (%r11)
jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++
2:
.if $0 != STRET
cmpq (%r11), %a2 // if (bucket->sel != _cmd)
.else
cmpq (%r11), %a3 // if (bucket->sel != _cmd)
.endif
jne 1b // scan more
// CacheHit must always be preceded by a not-taken `jne` instruction
CacheHit $0, $1 // call or return imp
3:
// double wrap or miss
jmp LCacheMiss_f
.endmacro
这段汇编代码大概的意思是在这个 CacheLookup
函数中,不断的通过 _cmd
与 cache
中的 bucket->sel
进行比较。如果 bucket->sel < 1
,则跳转到 LCacheMiss_f
标记去继续执行。程序跳到 LCacheMiss
,就说明 cache
中无缓存,未命中缓存,则要去 MethodTableLookup
查找。如果 bucket->sel == _cmd
即在 cache
中找到了相应的 SEL
,则直接执行该 IMP
。