前言
我们知道,objective-c中我们调用方法之后,底层会对方法进行缓存,让后面再调用更加快捷。今天我们主要研究是在方法没有缓存时,底层的查找流程。
调试分析
我们首先通过断点+汇编的方式来跟踪代码的运行流程。我们在方法调用时进行了断点,然后xcode->Debug->Debug Workflow->Always Show Disassembl选中,当我们运行到断点时候我们会看到汇编的信息。
0x100000cf3 <+51>: movq 0x14d6(%rip), %rsi ; "sayHello"
0x100000cfa <+58>: callq *0x300(%rip) ; (void *)0x00000001002c1500: objc_msgSend
我们看到方法进入了objc_msgSend。我们在这一行再打一个断点,然后ctrol+step into。
0x1002c158a <+138>: leaq 0x72dcf(%rip), %r10 ; objc_debug_taggedpointer_classes
0x1002c1591 <+145>: movq (%r10,%r11,8), %r10
0x1002c1595 <+149>: leaq 0x72bcc(%rip), %r11 ; (void *)0x0000000100334118: __NSUnrecognizedTaggedPointer
0x1002c159c <+156>: cmpq %r10, %r11
0x1002c159f <+159>: jne 0x1002c151a ; <+26>
0x1002c15a5 <+165>: movl %edi, %r11d
0x1002c15a8 <+168>: shrl $0x4, %r11d
0x1002c15ac <+172>: andl $0xff, %r11d
0x1002c15b3 <+179>: leaq 0x72e26(%rip), %r10 ; objc_debug_taggedpointer_ext_classes
0x1002c15ba <+186>: movq (%r10,%r11,8), %r10
0x1002c15be <+190>: jmp 0x1002c151a ; <+26>
0x1002c15c3 <+195>: jmp 0x1002c2050 ; _objc_msgSend_uncached
我们这里发现了一个_objc_msgSend_uncached方法,我们在_objc_msgSend_uncached这行断点,然后ctrol+step into。
0x1002c2094 <+68>: callq 0x1002e6840 ; lookUpImpOrForward at objc-runtime-new.mm:6095
在这里我们找到了lookUpImpOrForward而且根据后面的objc-runtime-new.mm,我们可以知道这是
objc的源码中的内容,我们可以去开源的objc源码中去查看。在objc源码objc-runtime-new.mm的6095行我们找到了这个方法的源码实现。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
...
}
源码流程分析
我们找到了lookUpImpOrForward的源码实现,接下来我们就一步一步的分析具体的流程。
第一步快速查找,先走一遍从缓存查找,防止多线程调用时已经缓存了。
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
第二步 ,判断是否是一个已知的类,如果是已知类即已经加载的类。如果不是已知类,就报错。
checkIsKnownClass(cls);//判断类是否加载了
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
if (slowpath(!isKnownClass(cls))) {
_objc_fatal("Attempt to use unknown class %p.", cls);
}
}
第三步,这里会看initialize是否有缓存,如果没有会调用initializeAndLeaveLocked,先调用一遍initialize方法加入缓存中。这也是initialize为什么说是在第一个消息发送之前被调用的原因。
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
第四步,for循环按照类继承链或者元类继承链的顺序查找。
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
- 当前cls的方法列表中使用了二分查找算法查找,如果找到,则进入cache缓存流程,并返回imp,如果没有找到,则返回nil.
getMethodNoSuper_nolock->search_method_list_inline->findMethodInSortedMethodList
Method meth = getMethodNoSuper_nolock(curClass, sel);//内部实现使用调用了findMethodInSortedMethodList
ALWAYS_INLINE 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);//向右偏移一位,即等价于/2,二分查找
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;
}
- 将cls赋值为父类,如果父类等于nil,则imp = 消息转发,并终止递归,进入第五步
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
- 如果父类链中存在循环则报错,中止。
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
-
父类缓存中查找方法
如果没有找到,break,继续循环。
如果找到,则直接返回imp,写入cache缓存。
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
第五步,动态方法决议。
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
最后如果还是没有找到,则会调用__objc_msgForward_impcache->__objc_msgForward->__objc_forward_handler,在源码中查找到void _objc_forward_handler = (void)objc_defaultForwardHandler,objc_defaultForwardHandler的实现源码。打印出崩溃信息。
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}