如果给一个对象发送消息,而该对象并没有实现该方法,则程序在崩溃之前会有两次挽回的机会:
-
动态方法解析
--A.如果发送的消息是实例方法(-)
Screen Shot 2018-05-30 at 08.36.21.png
如果是OC方法,使用上图上面框中的方式;
如果是C类型的方法,使用下面框中的方式。
-- B.如果发送的消息是类方法(+)
与上面的实现只有两点区别,
第一,调用的是resolveClassMethod:
方法;
第二就是在使用class_addMethod()
方法是,第一个参数不能是self
,因为self表示类对象,而类方法应该添加到元类对象中,因此第一个参数应该为:objc_getClass(self)
。
void other(id self, SEL _cmd) {
}
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(test)) {
class_addMethod(object_getClass(self),
sel,
(IMP)other,
"v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
- 消息转发
--A.转发实例方法
Screen Shot 2018-05-30 at 10.52.36.png- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
return [[MJCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
如果上面的方法返回nil则执行下面的方法:
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
// 获取传入的参数
int a;
[anInvocation getArgument:&a atIndex:2]; // index从2开始,前两个存储的是默认的隐含参数(self 和 _cmd)
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
// 消息转发后,用来获取方法的返回值
int returnValue;
[anInvocation getReturnValue:&returnValue];
}
--B.转发类方法
注意:方法开头的"+"以及返回的是类对象
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
return[MJCat class];
}
return [super forwardingTargetForSelector:aSelector];
}
如果上面的方法返回nil则执行下面的方法:
// 方法签名:返回值类型、参数类型
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
// 获取传入的参数
int a;
[anInvocation getArgument:&a atIndex:2]; // index从2开始,前两个存储的是默认的隐含参数(self 和 _cmd)
[anInvocation invokeWithTarget:[MJCat class];
// 消息转发后,用来获取方法的返回值
int returnValue;
[anInvocation getReturnValue:&returnValue];
}
利用消息转发降低因unrecognized selector sent to instance
导致的崩溃率,收集unrecognized selector sent to instance
的错误信息
在NSObject的Category中重写这两个方法,那么当有不能识别的方法被发送给接收者时,不会导致崩溃,同时可以收集错误信息。
#import "NSObject+Unrecognized.h"
@implementation NSObject (Unrecognized)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
@end
``
网友评论