方法的查找流程
- 对象方法测试
- 对象的实例方法 - 自己有
- 对象的实例方法 – 自己没有 – 找父类的
- 对象的实例方法 – 自己没有 – 父类没有 – 找父类的父类 – NSObject
- 对象的实例方法 – 自己没有 – 父类没有 – 找父类的父类 – NSObject也没有 – 崩溃
- 总结: SelfClass -> SuperClass ->...->NSObject -> nil
- 类方法测试
- 类方法 – 自己有
- 类方法 – 自己没有 – 父类有
- 类方法 – 自己没有 – 父类没有 – 父类的父类 – NSObject
- 类方法 – 自己没有 – 父类没有 – 父类的父类 – NSObject也没有 – 崩溃
- 类方法 – 自己没有 – 父类没有 – 父类的父类 – NSObject也没有 – 但是有对象方法,调用这个对象方法
- 总结:SelfMetaClass -> SuperMetaClass -> ... -> NSObjectMetaClass-> NSObject -> nil
- 这个有个点,类方法在元类中是以实例(对象)方法的形式存在的,所以例如 LGPerson 调用 一个NSObject的实例方法,这样也是可以的。
- 动态方法决议
- 消息转发
- 如果找不到,报错崩溃
- 无法识别的选择器发送给实例
2020-01-20 21:03:41.414708+0800 LGTest[1213:36305]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[LGPerson sayHello]: unrecognized selector sent to instance 0x101848d30'
流程分析
objc_msgSend 先到缓存进行快速查找,如果找不到,则进行慢速常规查找
快速查找的流程就是前面分析的消息发送流程,那么现在来看 _class_lookupMethodAndLoadCache3 普通查找流程
objc_calss_old.mm 中
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
lookUpImpOrForward分析
这个方法很重要,一定要记住
- 首先进行一系列准备,保证当对象方法或者类方法没有找到时,能够有父类或者元类能够去让我们去继续去查找,一系列递归
慢速查找,通过 obcj_msgSend 快速查找没有找到,需要到类里面进行慢速查找
-
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; runtimeLock.assertUnlocked(); 传进来的cache 是NO 所以不用看这一部分 接下来是判断是否是非法的类,判断也不用看 if (!cls->isRealized()) { 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); if (imp) goto done; 下边加括号的目的是 实现局部作用域,两个作用域内的变量名重复不会冲突 // Try this class's method lists. { 去当前类的method_list 中查找方法 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. { 当前类的父类中的查找方法 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. 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; }
先准备类(包括父类和元类),然后到类或父类的 method_list 查找方法。先判断一下当前imp有没有被缓存,如果存在缓存那么直接调用即可(这个时候可能有缓存进来)
之后通过 getMethodNoSuper_nolock 进行方法列表查找
static Class realizeClass(Class cls)
{
runtimeLock.assertLocked();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
。。。。。。
supercls = realizeClass(remapClass(cls->superclass)); 递归实现父类
metacls = realizeClass(remapClass(cls->ISA())); 递归实现元类
#if SUPPORT_NONPOINTER_ISA
。。。。。。
// SUPPORT_NONPOINTER_ISA
#endif
。。。。。
// Attach categories
methodizeClass(cls);
return cls;
}
getMethodNoSuper_nolock
在 getMethodNoSuper_nolock 方法内部,通过对 methods 列表进行遍历,找到当前sel对应的method,如果找不到则返回nil
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;
}
在 search_method_list 过程之后,通过 findMethodInSortedMethodList(sel, mlist) 进行二分查找,保证能够快速寻找到目标方法。
具体的二分查找代码
// 关键二分查找代码
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--;
}
}
找到 method_t 返回,如果没找到继续下一步,找到后然后 cache_fill 去缓存
方法被找到,进入 log_and_fill_cache 方法,内部调用 cache_fill 进入缓存流程,之后进入goto done
继续查找的流程
1)、如果自己没有,去查找父类。
2)、先看父类缓存有没有 imp = cache_getImp(cls, sel)
没有再到父类的方法列表去查 Method meth = getMethodNoSuper_nolock(cls, sel)
3)、如果父类还没找到,类方法会去找父类的元类
class curClass = cls –> superclass (元类的的父类,就是父类的元类)
同上查找
4)、如果父类的元类中也没有找到,就去NSObject中找
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);
}
如果以上流程方法还是没有找到,那么首先会进行动态方法解析 _class_resolveMethod, 在这个过程中,系统会调用一次已经存在的事先定义好的两个类方法,提供一次容错的机会。如果不想让程序因为查找不到方法就奔溃可以在这里进行处理,可以打印错误提示等。
// objc源码
_class_resolveInstanceMethod(cls, sel, inst)
_class_resolveClassMethod(cls, sel, inst)
// NSObject内部方法
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
如果系统给的这两个方法还是没有有做任何处理,那么就会进入消息转发阶段,消息转发阶段全局搜索一下该方法。在其汇编方法内部,调用了__objc_msgForward
imp = (IMP)_objc_msgForward_impcache;
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
全局搜索 __objc_forward_handler(汇编的在c++中要去掉一个下划线),找到了 objc_defaultForwardHandler 方法的具体实现
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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
如果什么都没处理,也找不到方法就会崩溃报错
2020-01-20 21:03:41.414708+0800 LGTest[1213:36305]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[LGPerson sayHello]: unrecognized selector sent to instance 0x101848d30'
总结
- 方法查找,先是通过 objc_msgSend 的快速缓存查找,之后再通过对类以及父类的方法列表进行二分法常规查找
- 最后如果都没有找到需要的方法,那么先进入动态方法解析,之后进入消息的快速转发和常规转发,消息转发处理失败之后则报错 crash
引申:
如果方法的查找失败就会崩溃,有时候我们不想让崩溃就可以通过消息转发机制来处理
方法查找流程 分为快速流程 慢速流程
消息转发机制也分为快速流程 慢速流程