前言
我們知道,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
我們這里發(fā)現(xiàn)了一個_objc_msgSend_uncached方法,我們在_objc_msgSend_uncached這行斷點,然后ctrol+step into。
0x1002c2094 <+68>: callq 0x1002e6840 ; lookUpImpOrForward at objc-runtime-new.mm:6095
在這里我們找到了lookUpImpOrForward而且根據(jù)后面的objc-runtime-new.mm,我們可以知道這是
objc的源碼中的內容,我們可以去開源的objc源碼中去查看。在objc源碼objc-runtime-new.mm的6095行我們找到了這個方法的源碼實現(xiàn)。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
...
}
源碼流程分析
我們找到了lookUpImpOrForward的源碼實現(xiàn),接下來我們就一步一步的分析具體的流程。
第一步快速查找,先走一遍從緩存查找,防止多線程調用時已經(jīng)緩存了。
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
第二步 ,判斷是否是一個已知的類,如果是已知類即已經(jīng)加載的類。如果不是已知類,就報錯。
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為什么說是在第一個消息發(fā)送之前被調用的原因。
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循環(huán)按照類繼承鏈或者元類繼承鏈的順序查找。
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);//內部實現(xiàn)使用調用了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 = 消息轉發(fā),并終止遞歸,進入第五步
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
- 如果父類鏈中存在循環(huán)則報錯,中止。
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
-
父類緩存中查找方法
如果沒有找到,break,繼續(xù)循環(huán)。
如果找到,則直接返回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;
}
第五步,動態(tài)方法決議。
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的實現(xiàn)源碼。打印出崩潰信息。
// 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);
}