说到OC 不得不说一下OC 的消息转发机制;
何为OC 的消息转发机制;其实就是这样的;
[objc func] 等同于 objc_msgSend(objc,@selector(func))
Objc 在向一个对象发送消息时,Runtime
库会根据对象的isa 指针找到对象实际所属的类,然后在该类的方法列表以及其父类方法列表中寻找方法运行,如果在最顶层的父类中依然找不到相映的方法时,程序在运行的时候会崩溃并抛出异常 unrecognized selector sent to xxxx
,在这之前Runtime
会给出三次机会拯救程序崩溃的机会;
- 第一次 实现
+resolveInstanceMethod:
或者+resolveClassMethod:
方法,拯救程序- 第二次 实现
-forwardingTargetForSelector:
或者+ forwardingTargetForSelector:
方法,拯救程序- 第三次,程序首先会发送
-methodSignatureForSelector
消息获得函数的参数和返回值类型,如果-methodSignatureForSelector
返回nil,RunTime 则会发出- doesNotRecognizeSelector:
消息,程序会挂掉,如果返回一个签名函数,Runtime 就会创建一个NSInvocation 对象并发送-forwardInvocation:
消息给目标对象;
具体逻辑示意图,可看此图
三次找不到方法的逻辑图
这里举例说明
创建一个Student
对象,如下
#import <Foundation/Foundation.h>
@interface Student : NSObject
-(void)instance_doSomething1;
+(void)class_doSomething1;
-(void)instance_doSomething2;
+(void)class_doSomething2;
-(void)instance_doSomething3;
+(void)class_doSomething3;
@end
//----------
#import "Student.h"
@implementation Student
@end
我们在一个viewController
调用 Student
的对象方法和类方法,分别分三次情况去解救程序,让程序免于崩溃;
- (void)viewDidLoad {
[super viewDidLoad];
Student *studentOBJ = [Student new];
/* 分别调用实例方法和类方法使程序崩溃*/
/* 在第一步解救程序*/
[studentOBJ instance_doSomething1];
[Student class_doSomething1];
/* 在第二步解救程序*/
[studentOBJ instance_doSomething2];
[Student class_doSomething2];
/* 在第三步解救程序*/
[studentOBJ instance_doSomething3];
[Student class_doSomething3];
}
1.第一次解决的机会 resolveInstanceMethod
给实例添加方法
/* 解救找不到方法的第一次机会 (实例)*/
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(instance_doSomething1)) {
class_addMethod(self, sel, (IMP)customerInstancePlayMusic, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
/* 我这里起个新名字,实现了Student 对象崩溃的 instancePlayMusic 方法 */
void customerInstancePlayMusic(id self,SEL _cmd){
NSLog(@"Instance implementation instance_doSomething1");
}
/*运行结果*/
runtime[54671:2198521] Instance implementation instance_doSomething1
给类添加方法
/* 解救找不到方法的第一次机会 (实例)*/
+(BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector(class_doSomething1)) {
NSString *className = NSStringFromClass([self class]);
Class metaClass = objc_getMetaClass("Student");
class_addMethod(metaClass, sel, (IMP)customerClassPlayMusic, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
void customerClassPlayMusic(id self,SEL _cmd){
NSLog(@"Class implementation class_doSomething1");
}
/*运行结果*/
runtime[54829:2212228] Class implementation class_doSomething1
2.第二次解救程序的机会
假如第一次没有实现,或者说觉得在程序中添加很多这样的分类方法,并不完美,想把崩溃的方法统一给一个类处理,然后埋点顺便报告给服务端;可以尝试一下第二次解救的机会
创建一个工具类,我们就给它命名 为StudentTool
类吧,定义如下
#import <Foundation/Foundation.h>
@interface StudentTool : NSObject
@end
//---------------------
#import "StudentTool.h"
@implementation StudentTool
/* 实现了Student 的实例2 和类方法2 */
-(void)instance_doSomething2{
NSLog(@" StudentTool 实现了实例 方法 instance_doSomething2");
}
+(void)class_doSomething2{
NSLog(@" StudentTool 实现了类 方法 class_doSomething2");
}
/* 实现了Student 的实例3方法和类方法3 */
-(void)instance_doSomething3{
NSLog(@" StudentTool 实现了实例 方法 instance_doSomething3 xxxx");
}
+(void)class_doSomething3{
NSLog(@" StudentTool 实现了类 方法 class_doSomething3 xxxx");
}
@end
Student+AvoidCrash
中的代码实现如下,可以在 forwardingTargetForSelector
方法中将消息转发给别的类(这里 以 StudentTool类)实现-(void)instance_doSomething2{}
和+(void)class_doSomething2{}
不存在的方法,从而避免崩溃;
消息转发解决对象方法的崩溃
/* 消息转发解决实例崩溃*/
/* 第二步*/
/* 实例方法的消息转发*/
-(id)forwardingTargetForSelector:(SEL)aSelector
{
return [StudentTool new];
}
/*运行结果*/
runtime[55832:2296221] StudentTool 实现了实例 方法 instance_doSomething2
消息转发解决类方法的崩溃
/* 类方法的消息转发*/
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
return [StudentTool class];
}
/*运行结果*/
//程序不在崩溃,方法转发到类 StudentTool 类的 同名方法中
runtime[55832:2296221] StudentTool 实现了类 方法 class_doSomething2
3.第三次解救程序免于崩溃的情况
-forwardInvocation:
在一个对象无法识别消息之前调用,再需要重载-methodSignatureForSelector:
,因为在调用-forwardInvocation:
之前是要把消息打包成NSIvocation对象的,所以需要-methodSignatureForSelector:
重载,如果不能在此方法中不能不为空的NSMethodSignature对象,程序依然会崩溃;
Student+AvoidCrash.m
中代码实现如下
/* 第一、二步没有实现,通过第三步来解决程序异常崩溃*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(instance_doSomething3)) {
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
/* 如果 StudentTool 这个类能相映这个 崩溃方法,就交给 StudentTool 来响应*/
if ([StudentTool instancesRespondToSelector:aSelector]) {
methodSignature = [StudentTool instanceMethodSignatureForSelector:aSelector];
}
}
return methodSignature;
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector == @selector(doSomething3:)) {
StudentTool *instance = [StudentTool new];
if ([instance respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:instance];
}
}
}
如果有不清楚的地方,可以查看github源码 https://github.com/hunter858/runtime
网友评论