前言
当消息进行快速慢速查找后,依然没找到,也没进行处理时,会报错unrecognized selector,在报错前,如果进行了适当的处理,也许不会报错。
方法未实现报错
🌰
@interface SLPerson : NSObject
- (void)say1;
- (void)say2;
+ (void)say3;
@end
@implementation SLPerson
- (void)say1{
NSLog(@"SLPerson say : %s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SLPerson * p = [[SLPerson alloc]init];
[p say2];
[SLPerson say3];
}
return 0;
}
运行代码,会出现以下的报错
2023-02-09 17:34:03.505342+0800 KCObjcBuild[4308:96734] -[SLPerson say2]: unrecognized selector sent to instance 0x600000008030
2023-02-09 17:34:03.512201+0800 KCObjcBuild[4308:96734] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SLPerson say2]: unrecognized selector sent to instance 0x600000008030'
2023-02-09 17:40:17.821639+0800 KCObjcBuild[4455:102062] +[SLPerson say3]: unrecognized selector sent to class 0x100008158
2023-02-09 17:40:17.822475+0800 KCObjcBuild[4455:102062] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[SLPerson say3]: unrecognized selector sent to class 0x100008158'
由慢速查找源码得知,最后报错是走__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,发现本质是调用objc_defaultForwardHandler
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
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;
三次方法查找的挽救机会
- 动态方法决议
- 快速转发
- 慢速转发
动态方法决议
动态方法决议源码实现如下:
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);
}
流程图(转载自 Style_月月,感谢🙏)如下:
当类没实现实例方法say2时,可以通过以下方式进行补救
- (void)say2_1{
NSLog(@"SLPerson say : %s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(say2)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(say2_1));
//获取sayMaster的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(say2_1));
//获取sayMaster的丰富签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
当类没实现类方法say3时,可以通过以下方式进行补救
+(void)say3_1{
NSLog(@"SLPerson say : %s",__func__);
}
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(say3)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("SLPerson"), @selector(say3_1));
Method slClassMethod = class_getInstanceMethod(objc_getMetaClass("SLPerson"), @selector(say3_1));
const char *type = method_getTypeEncoding(slClassMethod);
return class_addMethod(objc_getMetaClass("SLPerson"), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
优化
由方法慢速查找的路径可知,实例方法(类-
父类-
根类-
nil)、类方法(元类-根元类-根类-nil),c路径都会经过根类(即NSObject),所以可以直接在NSObject分类进行处理
#import "NSObject+test.h"
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(say2)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
//获取sayMaster方法的imp
IMP imp = class_getMethodImplementation(self, @selector(say2_1));
//获取sayMaster的实例方法
Method sayMethod = class_getInstanceMethod(self, @selector(say2_1));
//获取sayMaster的丰富签名
const char *type = method_getTypeEncoding(sayMethod);
//将sel的实现指向sayMaster
return class_addMethod(self, sel, imp, type);
} else if (sel == @selector(say3)) {
NSLog(@"%@ 来了", NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(objc_getMetaClass("SLPerson"), @selector(say3_1));
Method slClassMethod = class_getInstanceMethod(objc_getMetaClass("SLPerson"), @selector(say3_1));
const char *type = method_getTypeEncoding(slClassMethod);
return class_addMethod(objc_getMetaClass("SLPerson"), sel, imp, type);
}
return NO;
}
消息转发流程
消息转发分为
1.快速转发 :forwardingTargetForSelector
2.慢速转发:methodSignatureForSelector、forwardInvocation
快速转发
SLPerson类重写forwardingTargetForSelector,将消息接收者指定为SLStudent,SLStudent中有say具体实现
🌰代码
@interface SLStudent : NSObject
- (void)say;
@end
@implementation SLStudent
- (void)say{
NSLog(@"SLStudent say : %s",__func__);
}
@end
@interface SLPerson : NSObject
- (void)say;
@end
@implementation SLPerson
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
//将消息的接收者为SLStudent的一个实例
return [[SLStudent alloc]init];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
SLPerson * p = [[SLPerson alloc]init];
[p say];
}
return 0;
}
2023-02-10 16:35:31.349392+0800 KCObjcBuild[18227:300068] -[SLPerson forwardingTargetForSelector:] - say
2023-02-10 16:35:31.350415+0800 KCObjcBuild[18227:300068] SLStudent say : -[SLStudent say]
慢速转发
🌰代码
即使不在forwardInvocation进行处理,也不会报错
@implementation SLPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
}
@end
2023-02-10 16:41:10.124284+0800 KCObjcBuild[18514:303965] -[SLPerson methodSignatureForSelector:] - say
2023-02-10 16:41:10.125004+0800 KCObjcBuild[18514:303965] -[SLPerson forwardInvocation:] - <NSInvocation: 0x104007d80>
当然正常来说还是会对forwardInvocation进行处理的,即可完成消息慢速转发。需在SLPerson中定义一个SLStudent类型的成员,作为转发的target。
@interface SLPerson : NSObject {
SLStudent * _student;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
anInvocation.target = _student;
[anInvocation invoke];
}
2023-02-10 16:51:40.640554+0800 KCObjcBuild[18690:311853] -[SLPerson methodSignatureForSelector:] - say
2023-02-10 16:51:40.641265+0800 KCObjcBuild[18690:311853] -[SLPerson forwardInvocation:] - <NSInvocation: 0x100504400>
2023-02-10 16:51:40.641398+0800 KCObjcBuild[18690:311853] SLStudent say : -[SLStudent say]
执行两次动态方法决议
当走到消息快速转发这一步且forwardingTargetForSelector中返回nil的时候,发现整个流程动态决议方法总共会走两次。
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return nil;
}
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
// NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
// return [NSMethodSignature signatureWithObjCTypes:"v@:"];
//}
//
//- (void)forwardInvocation:(NSInvocation *)anInvocation{
// NSLog(@"%s - %@",__func__,anInvocation);
// anInvocation.target = _student;
// [anInvocation invoke];
//}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(say)) {
NSLog(@"%s - %@ 来了",__func__, NSStringFromSelector(sel));
}
return [super resolveInstanceMethod:sel];
}
2023-02-10 17:19:01.941588+0800 KCObjcBuild[19122:329964] +[SLPerson resolveInstanceMethod:] - say 来了
2023-02-10 17:19:01.942366+0800 KCObjcBuild[19122:329964] -[SLPerson forwardingTargetForSelector:] - say
2023-02-10 17:19:01.942609+0800 KCObjcBuild[19122:329964] +[SLPerson resolveInstanceMethod:] - say 来了
2023-02-10 17:19:01.942782+0800 KCObjcBuild[19122:329964] -[SLPerson say]: unrecognized selector sent to instance 0x104006d30
总结