在慢速查找中,对慢速查找未找到后的处理,并没有做详细解析,因为其内容同样涉及了方法查找流程中很重要的知识点,所以单独写。
动态方法决议
在慢速查找仍未找到结果时,并不会直接就报错unrecognized selector sent to instance。在报错之前,Runtime会给一次动态方法决议的机会。
从之前慢速查找的lookUpImpOrForward源码最后
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
其中resolveMethod_locked就会进入动态方法决议的部分。
对象方法
首先看下resolveInstanceMethod对象方法动态决议源码实现
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// lookup 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));
}
}
}
其中最关键的部分在于:
SEL resolve_sel = @selector(resolveInstanceMethod:);
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
IMP imp = lookUpImpOrNil(inst, sel, cls);
-
- 进行一次消息发送
msg(cls, resolve_sel, sel);消息的接收者为原来的cls,消息的方法主体为@selector(resolveInstanceMethod:)
- 进行一次消息发送
-
- 消息发送后,调用了
lookUpImpOrNil,其实它仍是慢速查找的方法调用
- 消息发送后,调用了
在查找不到对象方法时,系统会进行resolveInstanceMethod的消息发送,查找类cls中是否实现有该方法,假如代码中添加了resolveInstanceMethod:
@interface LGPerson : NSObject
- (void)sayHello;
@end
@implementation LGPerson
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"对象方法决议");
return [super resolveInstanceMethod:sel];;
}
@end
LGPerson *person = [LGPerson alloc];
[person sayHello];
可以看到,在LGPerson中实现了resolveInstanceMethod函数,当消息查找不到,进行动态方法决议时,就会调用到LGperson中的resolveInstanceMethod。我们可以在这一层,对没有找到的方法,做一些调整:
@interface LGPerson : NSObject
- (void)sayHello;
- (void)sayBye;
@end
#import <objc/message.h>
@implementation LGPerson
- (void)sayBye{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"对象方法决议");
if (sel == @selector(sayHello)) {
IMP imp = class_getMethodImplementation(self, @selector(sayBye));
Method sayByeM = class_getInstanceMethod(self, @selector(sayBye));
const char *type = method_getTypeEncoding(sayByeM);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];;
}
@end
LGPerson *person = [LGPerson alloc];
[person sayHello];
resolveInstanceMethod动态方法决议中,当判断通过时,在LGPerson中class_addMethod添加了一个方法sayBye,即当查找不到sayHello时,动态添加个方法,让系统去查找这个新的方法sayBye。
类方法
首先看下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));
}
}
}
可以看到,类方法的动态决议和对象方法雷同,同样是:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
IMP imp = lookUpImpOrNil(inst, sel, cls);
原理基本一样,同样在LGPerson中实现resolveClassMethod:
@interface LGPerson : NSObject
+ (void)say666;
+ (void)say999;
@end
#import <objc/message.h>
@implementation LGPerson
+ (void)say999{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"类方法决议");
if (sel == @selector(say666)) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(say999));
Method say999M = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(say999));
const char *type = method_getTypeEncoding(say999M);
return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
@end
[LGPerson say666];
类方法存在元类信息中,类方法相当于元类对象的对象方法。所以
class_getMethodImplementation(objc_getMetaClass("LGPerson")、class_getInstanceMethod(objc_getMetaClass("LGPerson")。
但是,源码中调用resolveClassMethod后,为什么又要判断当找不到时,就去调用对象方法动态决议呢?
还是要从isa走位图来看
类方法保存在元类信息中,对象方法保存在类信息中,在OC底层他们基本没有区别,
+ -来区分类方法和对象方法,只是在上层人为的区分。
根据元类的继承链,当慢速查找类方法一直找到根类NSObject仍找不到时,通过调用resolveClassMethod添加类方法后,是从根类NSObject中找不到的lookUpImpOrNil(inst, sel, cls)为空。因为会添加到根元类NSObject中去,所以需要调用resolveInstanceMethod。
消息转发
当在动态方法决议这次机会中,返回NO,没有做调整或操作时,会进入到消息转发流程。
但是在动态方法决议后,没看到有关消息转发的方法,要该如何查看呢?
利用instrumentObjcMessageSends方法监控OC底层消息发送。
赋值objcMsgLogEnabled,可以认为是消息日志的开关。
@interface LGPerson : NSObject
- (void)sayHello;
@end
@implementation LGPerson
@end
extern void instrumentObjecMessageSends(BOOL flag);
instrumentObjecMessageSends(YES);
[person sayHello];
instrumentObjecMessageSends(NO);
日志文件中,当动态方法决议之后,调用了
forwardingTargetForSelector和methodSignatureForSelector
消息快速转发
forwardingTargetForSelector就是消息快速转发的函数。当在类中找不到方法,就返回一个第一接收者来接盘。
代码看如何利用:
#import "LGTercher.h"
@interface LGPerson : NSObject
- (void)sayHello;
@end
@implementation LGPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
if ([NSStringFromSelector(aSelector) isEqualTo:@"sayHello"]) {
return [LGTercher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@interface LGTercher : NSObject
- (void)sayHello;
@end
@implementation LGTercher
- (void)sayHello{
NSLog(@"%s",__func__);
}
@end
LGPerson *person = [LGPerson alloc];
[person sayHello];
此时就看到,
sayHello的方法接收者变成了LGTercher。当然,当LGTercher中也没有sayHello方法时,同样会报错unrecognized selector..。
消息慢速转发
当没有处理消息快速转发时,会进入到消息慢速转发methodSignatureForSelector。此时可以调整的就不只是消息接收者了。并且需要同时实现- forwardInvocation:方法
需要在
消息慢速转发methodSignatureForSelector方法中,返回一个NSMethodSignature方法签名对象。如果其中返回nil的话,慢速转发也就不会进入到forwardInvocation方法进行处理了。
在NSInvocation中,可以处理方法的target、selector等,对查找不到的方法做统一调整。
慢速转发代码:
@interface LGPerson : NSObject
- (void)sayHello;
@end
@implementation LGPerson
// 慢速转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
// 返回"v@:" 详见Type Encodings
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
LGTercher *t = [LGTercher alloc];
anInvocation.target = t;
anInvocation.selector = @selector(sayBye);
[anInvocation invoke];
}
@end
@interface LGTercher : NSObject
- (void)sayBye;
@end
@implementation LGTercher
- (void)sayBye{
NSLog(@"%s",__func__);
}
@end
LGPerson *person = [LGPerson alloc];
[person sayHello];
到此消息流程就结束了,当慢速转发也没有做处理时,就会抛出unrecognized selector..。
流程图: