注册

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流程分析

堆栈信息-->第一次tryCache会把我动态添加的方法存进cache

本次TryCache,会调用lookUpImpOrForWard函数查找MethodTable。入参behavior值为4,找不到imp的话不会再走动态决议和消息转发,直接return nil,分支如下:

    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
所以这次tryCache实际的作用就是在动态决议添加方法之后,找到方法,并调用log_and_fill_cache函数存进缓存(佐证了下面这段注释)

   // 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还能解决类方法缺失的问题?

关于这个问题,我们来看张经典的
c0fe94194e9641a464628e3371e685a9.png

如果我们查找一个类方法沿着继承链最终会找到NSObject(rootMetaClass的父类是NSObject),这会导致一个有意思的问题:我们的NSObject对象方法可以响应类方法的sel

看个实例

给NSObect添加个instaceMethod


b5d8ddeb142a2ae5fb26f5379f408f18.pngb01c5ff47448d823524841207495b49a.png
是不是很惊喜,其实我们底层对classMethod和InstanceMethod根本没有区分,classMethod也是InstanceMethod

* 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);
}
只不过,找classMethod是从MetaClass查找InstanceMethod,找InstanceMethod是从class找InstanceMethod。
透过现象看本质,这里就可以解释,为什么resolveClass完毕,缓存中找不到imp,会再次调用resolveInstance。显然,我们给NSObject添加InstanceMethod可以解决问题,而且可以在这里我们也可以添加classMethod。毕竟classMethod也是InstanceMethod。






作者:可可先生_3083
链接:https://www.jianshu.com/p/2d1372b4d2c9





0 个评论

要回复文章请先登录注册