OC底层原理-动态方法决议
当lookupImpOrForward函数从cache和methodTable中找不到对应Method,继续向下执行就会来到resolveMethod_locked函数也就是我们常说的动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
resolveMethod_locked
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
只执行一次
behavior & LOOKUP_RESOLVER
behavior ^= LOOKUP_RESOLVER;
这俩步操作保证resolveMethod_locked只被执行一次
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
//根类NSObject有默认实现兜底,不会走到这里
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
//向cls对象发送resolveInstanceMethod:消息,参数为当前的sel
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//从方法缓存中再快速查找一遍
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
// NSObject有兜底实现
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
resolveMethod_locked会发送resolveInstanceMethod:和resolveClassMethod:消息,为了减少程序的崩溃提用户体验,苹果在这里给开发者一次机会去补救,这个过程就叫做动态方法决议。这里也体现了aop编程思想,在objc_msg流程中给开发者提供了一个切面,切入自己想要处理,比如安全处理,日志收集等等。
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
看完源码思考俩个问题
1.为什么在resloveInstanceMethod函数中调用了一次lookUpImpOrNilTryCache,resolveMethod_locked函数最后又调用了一次lookUpImpOrNilTryCache?这俩次分别有什么作用?
第一次TryCache流程分析
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
第二次TryCache流程分析
这次我们直接看注释吧
// chances are that calling the resolver have populated the cache
// so attempt using it
调用动态决议可能填充了得缓存,并尝试使用它。嗯,第二次tryCache的作用已经简单明了。
本次调用入参behavior值为1,methodTable查找不到imp不会走动态决议流程,但会调用消息转发
为什么分为俩次呢,一次不行吗?
为什么不最后查找方法,填充缓存再返回,反而要先填充缓存,再尝试从缓存中查找,这么做有什么好处呢?
有个关于多线程的猜想:
假如线程a发送消息s进入了动态决议流程,此时线程b也发送消息s,这时候如果缓存中有已添加的imp响应消息s,是不是就不会继续慢速查找,动态决议等后续流程。这么想,动态决议添加的方法是不是越先添加到缓存越好。
另外一点我们看到resolveClassMethod之后,也尝试从缓存中查找,而且找不到又调用了一遍resolveInstanceMethod。
可已看出苹果开发者在设计这段流程的思考🤔可能是:
既然你愿意通过动态方法决议去添加这个imp,费了这么大功夫,很显然你想使用该imp,而且使用的频率可能不低。既然如此在resolver方法调用完毕,我就帮你放进缓存吧。以后你想用直接从缓存中找。
2. 为什么类resolver之后会尝试调用instance的resolver?难道instance的resolver还能解决类方法缺失的问题?
如果我们查找一个类方法沿着继承链最终会找到NSObject(rootMetaClass的父类是NSObject),这会导致一个有意思的问题:我们的NSObject对象方法可以响应类方法的sel
看个实例
给NSObect添加个instaceMethod
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
透过现象看本质,这里就可以解释,为什么resolveClass完毕,缓存中找不到imp,会再次调用resolveInstance。显然,我们给NSObject添加InstanceMethod可以解决问题,而且可以在这里我们也可以添加classMethod。毕竟classMethod也是InstanceMethod。
作者:可可先生_3083
链接:https://www.jianshu.com/p/2d1372b4d2c9