在 iOS底层原理:objc_msgSend之缓存查找 和 iOS底层原理:objc_msgSend之慢速查找 中我们已经分析了,当方法在快速查找和慢速查找流程都未找到时,会走动态方法决议
和消息转发
流程。
动态方法决议
和消息转发
流程是苹果提供给我们解决方法未实现的兜底方法。当方法未在快速查找
和慢速查找
中找到实现时,会先走入动态方法决议
,如果动态方法决议
仍未对方法(即imp
)进行处理时,会走入消息转发
流程。
而消息转发
流程其实又分为了快速消息转发
和慢速消息转发
。
动态方法决议
在lookUpImpOrForward
方法中其实有个判断条件用于判断是否进行动态方法决议的条件判断:
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
分析下上面的代码:
- 1、首先分析两个参数
behavior
和LOOKUP_RESOLVER
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
-
behavior
:在快速查找的流程中我们知道behavior
其实就是LOOKUP_INITIALIZE | LOOKUP_RESOLVER
,而LOOKUP_INITIALIZE = 1
,LOOKUP_RESOLVER = 2
,那么|
运算后的结果为0011
,也就是3
,即behavior = 3
-
LOOKUP_RESOLVER
:搜索可得知LOOKUP_RESOLVER = 2
- 2、那么第一次
behavior & LOOKUP_RESOLVER
的结果为1
,会执行resolveMethod_locked
,同时behavior ^= LOOKUP_RESOLVER
后,behavior = 1
- 2.1、那么下次会直接再次运行
resolveMethod_locked
中的resolveInstanceMethod
方法,而在resolveInstanceMethod
中会调用lookUpImpOrNil
,也就是调用了lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL)
,此时再次走入lookUpImpOrForward
方法中,但是此时的behavior
为12
- 2.2、那么再次执行到
behavior & LOOKUP_RESOLVER
,也就是12 & 2
的结果1100 & 0010
,即0
,所以之间进入了下一步调用log_and_fill_cache
方法,这个方法就是将方法加入到缓存中。
- 2.1、那么下次会直接再次运行
- 3、执行完
resolveMethod_locked
中的resolveInstanceMethod
后,会再次执行一次lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE)
,此时的behavior
为1
,那么behavior | LOOKUP_CACHE
的结果为0001 | 0100
,即0101
,也就是5
- 4、当再次进入
lookUpImpOrForward
方法时,此时会先去缓存
中查找,因为在动态方法决议
的过程中,已经将该方法加入了缓存中,并且5 & 4
,结果为4
,所以此次会走done_nolock
,相关方法如下:
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
- 5、最后会执行判断
(behavior & LOOKUP_NIL) && imp == forward_imp
,即5 & 8 && 1
,结果是0
,直接返回了imp
- 6、最后会调用到
resolveInstanceMethod
方法,当发现resolveInstanceMethod
仍然为做处理时,会执行forwardingTargetForSelector
,当forwardingTargetForSelector
也未作处理时,会执行methodSignatureForSelector
,然后当methodSignatureForSelector
也未作处理时,会直接执行doesNotRecognizeSelector
方法
resolveMethod_lockedf分析
从iOS底层原理:objc_msgSend之慢速查找流程中,我们知道了最后在resolveMethod_locked
中走动态方法决议
流程。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
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 (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
在上面的源码中我们可以发现,动态方法决议
其实又细分了类方法的动态方法决议
和实例方法的动态方法决议
。
实例方法的动态方法决议
resolveInstanceMethod
源码如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
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 = lookUpImpOrNil(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));
}
}
}
1、在resolveInstanceMethod
中,首先调用lookUpImpOrNil
判断是否实现了resolveInstanceMethod
,也就是我们说的动态方法决议
2、然后会调用objc_msgSend
进行resolveInstanceMethod
的消息转发,并将未找到的sel
作为参数传出去
3、最后再次调用lookUpImpOrNil
进行查询原来的方法是否有实现,也就是第二步是否有将imp
进行处理,重新走lookUpImpOrForward
流程。
类方法的动态方法决议
resolveClassMethod
源码如下:
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(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 = lookUpImpOrNil(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));
}
}
}
类方法的动态方法决议方法实现,其实和实例方法的动态方法决议实现基本流程是一样的,唯一的区别是类方法的动态决议
在根元类还未找到之后,会调用一次根元类的指向的实例方法的实例方法的动态方法决议
(!lookUpImpOrNil(inst, sel, cls)
判断是否指向根元类),也就是如果类方法没有实现,则会去找同名的实例方法。这点在isa
的走位图中可以看出来。
消息转发
如果通过源码分析,其实到了动态方法决议resolveInstanceMethod
方法之后,我们就很难分析到接下来需要走的流程了。那么我们可以通过一个系统提供的方法instrumentObjcMessageSends
来查看下。可以发现我们可以再/tmp/
目录下找到对应的文件msgSends-%d"
。
调用instrumentObjcMessageSends
实现:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
instrumentObjcMessageSends(YES); // 开启
[person sayHello];
instrumentObjcMessageSends(NO); // 关闭
NSLog(@"Hello, World! %@",person);
}
return 0;
}
消息准发流程日志
通过日志文件也可以分析出,当执行完动态方法决议
时,会进入forwardingTargetForSelector
方法,也就是我们常说的快速消息转发
。
还可以通过hopper disassembler来进行二进制分析,也可以分析出流程。
快速消息转发
快速消息转发通过实现forwardingTargetForSelector
,我们可以看到确实走到了该方法内,那么我们要如何在这里面进行处理呢?
可以看到,当我们将当前的接受者指定为Student
时,会执行Student
中实现的方法。
那么我们在此时通过runtime
将改方法中获取到的aSelector
使用动态添加的方式(class_addMethod
),来实现,也可以进行快速消息转发
。
但是当我们在forwardingTargetForSelector
中也未进行处理时,会走入慢速消息转发
。
慢速消息转发
forwardingTargetForSelector描述 methodSignatureForSelector描述通过官方文档中的描述,我们可以知道需要在methodSignatureForSelector
中返回一个NSInvocation
对象后,才能进入forwardInvocation
方法。当返回nil或者self的时候,就会进入doesNotRecognizeSelector
方法。
从上图中我们可以看到,在anInvocation
中,有target
和selector
属性,那么我们可以动态的修改这几个属性,来实现定制化。
我们将原来Person
中的sayHello
方法,通过指定Student
中的sayHey
方法,来进行了消息转发。
如果在上诉几个流程中都未进行处理,则会进入我们常见的报错方法中了。即doesNotRecognizeSelector
。
网友评论