unrecognized selector sent to in
消息转发以及动态解析方法
消息转发机制基本上分为三个步骤:
- 动态方法解析
- 备用接收者
- 完整转发
首先,对于动态方法解析,NSObject提供了以下两个方法来处理:
+ (BOOL)resolveClassMethod:(SEL)name
+ (BOOL)resolveInstanceMethod:(SEL)name
从方法名我们可以看出,resolveClassMethod:是用于动态解析一个类方法;而resolveInstanceMethod:是用于动态解析一个实例方法。
我们知道,一个Objective-C方法是其实是一个C函数,它至少带有两个参数,即self和_cmd。我们使用class_addMethod函数,可以给类添加一个方法。我们以resolveInstanceMethod:为例,如果要给对象动态添加一个实例方法,则可以如下处理:
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
备用接收者
NSObject提供了以下方法来处理:
- (id)forwardingTargetForSelector:(SEL)aSelector
该方法返回未被接收消息最先被转发到的对象。如果一个对象实现了这个方法,并返回一个非空的对象(且非对象本身),则这个被返回的对象成为消息的新接收者。另外如果在非根类里面实现这个方法,如果对于给定的selector,我们没有可用的对象可以返回,则应该调用父类的方法实现,并返回其结果
完整转发
如果备用方法搞不定,需要包装成一个NSInvocation对象, 在这里要先返回一个方法签名
该方法的声明如下:
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
这个方法返回包含方法描述信息的NSMethodSignature对象,如果找不到方法,则返回nil。如果我们的对象包含一个代理或者对象能够处理它没有直接实现的消息,则我们需要重写这个方法来返回一个合适的方法签名。
对应于实例方法,当然还有一个处理类方法的相应方法,其声明如下:
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
另外,NSObject类提供了两个方法来获取一个selector对应的方法实现的地址,如下所示:
- (IMP)methodForSelector:(SEL)aSelector
+ (IMP)instanceMethodForSelector:(SEL)aSelector
获取到了方法实现的地址,我们就可以直接将IMP以函数形式来调用。
对于methodForSelector:方法,如果接收者是一个对象,则aSelector应该是一个实例方法;如果接收者是一个类,则aSelector应该是一个类方法。
对于instanceMethodForSelector:方法,其只是向类对象索取实例方法的实现。如果接收者的实例无法响应aSelector消息,则产生一个错误。
最后,对于完整转发,NSObject提供了以下方法来处理
- (void)forwardInvocation:(NSInvocation *)anInvocation
当前面两步都无法处理消息时,运行时系统便会给接收者最后一个机会,将其转发给其它代理对象来处理。这主要是通过创建一个表示消息的NSInvocation对象并将这个对象当作参数传递给forwardInvocation:方法。我们在forwardInvocation:方法中可以选择将消息转发给其它对象。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selStr = NSStringFromSelector(anInvocation.selector);
if ([selStr isEqualToString:@"deptName"]) {
[anInvocation setTarget:self.companyModel];
[anInvocation setSelector:@selector(deptName:)];
BOOL hasCompanyName = YES;
//第一个和第一个参数是target和sel
[anInvocation setArgument:&hasCompanyName atIndex:2];
[anInvocation retainArguments];
[anInvocation invoke];
}else{
[super forwardInvocation:anInvocation];
}
}
在开发中经常遇到一种错误,就是unrecognized selector sent to instance *** 但是针对未识别方法的崩溃处理。关键就是方法转发
#import "NSObject+UnRecognizedSelHandler.h"
#import <objc/runtime.h>
//提示框--->UIAlertController
#define ALERT_VIEW(Title,Message,Controller) {UIAlertAction *action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:Title message:Message preferredStyle:UIAlertControllerStyleAlert]; [alertVc addAction:action];[Controller presentViewController:alertVc animated:YES completion:nil];}
#import "AppDelegate.h"
static NSString *_errorFunctionName;
void dynamicMethodIMP(id self,SEL _cmd){
#ifdef DEBUG
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
UIViewController *currentRootViewController = delegate.window.rootViewController;
NSString *error = [NSString stringWithFormat:@"errorClass->:%@\n errorFuction->%@\n errorReason->UnRecognized Selector",NSStringFromClass([self class]),_errorFunctionName];
ALERT_VIEW(@"程序异常",error,currentRootViewController);
#else
//upload error
#endif
}
#pragma mark 方法调换
static inline void change_method(Class _originalClass ,SEL _originalSel,Class _newClass ,SEL _newSel){
Method methodOriginal = class_getInstanceMethod(_originalClass, _originalSel);
Method methodNew = class_getInstanceMethod(_newClass, _newSel);
method_exchangeImplementations(methodOriginal, methodNew);
}
@implementation NSObject (UnRecognizedSelHandler)
+ (void)load{
change_method([self class], @selector(methodSignatureForSelector:), [self class], @selector(SH_methodSignatureForSelector:));
change_method([self class], @selector(forwardInvocation:), [self class], @selector(SH_forwardInvocation:));
}
- (NSMethodSignature *)SH_methodSignatureForSelector:(SEL)aSelector{
if (![self respondsToSelector:aSelector]) {
_errorFunctionName = NSStringFromSelector(aSelector);
NSMethodSignature *methodSignature = [self SH_methodSignatureForSelector:aSelector];
if (class_addMethod([self class], aSelector, (IMP)dynamicMethodIMP, "v@:")) {
NSLog(@"临时方法添加成功!");
}
if (!methodSignature) {
methodSignature = [self SH_methodSignatureForSelector:aSelector];
}
return methodSignature;
}else{
return [self SH_methodSignatureForSelector:aSelector];
}
}
- (void)SH_forwardInvocation:(NSInvocation *)anInvocation{
SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
}else{
[self SH_forwardInvocation:anInvocation];
}
}
@end
原理就是运用oc的运行时,将系统的转发方法与自己的方法做替换,从而为这个对象重新绑定一个新的方法,新的方法会在当前页面弹出程序的错误信息。也可以在这里将错误的代码信息上传到自己的服务器中。