以前知道苹果执行方法是通过消息执行的,当对应的对象或者类无法处理该消息时,苹果就会启动消息转发机制,通过这一机制,我们可以告诉对象可以如何处理未知消息!
第一步:动态方法解析
对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了,例如:
void clickBtn(id self,SEL _cmd){
NSLog(@"通过动态方法解析处理未知消息%@ %p",self,_cmd);
}
//消息转发第一步,可以再基类里面实现该方法,用于处理不能执行的消息
+(BOOL)resolveInstanceMethod:(SEL)sel{
// NSString *selStr = NSStringFromSelector(sel);
//当当前实例未实现该方法时,给该实例动态添加方法。
class_addMethod(self.class, @selector(clickBtn), (IMP)clickBtn, "@:");
return [super resolveInstanceMethod:sel];
}
第二步:备用接收者
如果在上一步无法处理消息,则Runtime会继续调以下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。
使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。例如:
//消息转发第二步,转发给可以处理该消息的对象
-(id)forwardingTargetForSelector:(SEL)aSelector{
BYMsgTool *mesTool = [[BYMsgTool alloc]initWithSelector:aSelector];
return mesTool;
}
转发对象里面的实现(目前觉得这样可以统一处理无法点击的事件,设置一个专门处理无法处理消息的异常的类):
@implementation BYMsgTool
void notHaveSelector(){
NSLog(@"没有该方法");
}
-(instancetype)initWithSelector:(SEL)select{
if (self == [super init]) {
//当当前实例未实现该方法时,给该实例动态添加方法,做统一处理。
class_addMethod(self.class, select, (IMP)notHaveSelector, "@:");
}return self;
}
@end
第三步:完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
这个方法返回包含方法描述信息的NSMethodSignature对象,如果找不到方法,则返回ni,则我们需要重写这个方法来返回一个合适的方法签名(就是换个对象或者类,通过该方法返回一个NSMethodSignature的对象)实例:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"methodSignatureForSelector--%@",NSStringFromSelector(aSelector));
//实例方法返回NSMethodSignature对象
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([BYMsgTool instancesRespondToSelector:aSelector]) {
//类方法返回NSMethodSignature对象
signature = [BYMsgTool instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
对应于实例方法,当然还有一个处理类方法的相应方法,其声明如下:
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
另外,NSObject类提供了两个方法来获取一个selector对应的方法实现的地址,如下所示:
- (IMP)methodForSelector:(SEL)aSelector
+ (IMP)instanceMethodForSelector:(SEL)aSelector
获取到了方法实现的地址,我们就可以直接将IMP以函数形式来调用。
对于methodForSelector:方法,如果接收者是一个对象,则aSelector应该是一个实例方法;如果接收者是一个类,则aSelector应该是一个类方法。
对于instanceMethodForSelector:方法,其只是向类对象索取实例方法的实现。如果接收者的实例无法响应aSelector消息,则产生一个错误。
获取到NSInvocation 对象后会调用-(void)forwardInvocation:(NSInvocation *)anInvocation方法来进行消息的完整转发,示例代码:
-(void)forwardInvocation:(NSInvocation *)anInvocation{
BYMsgTool *mesTool = [[BYMsgTool alloc]initWithSelector:anInvocation.selector];
NSLog(@"forwardInvocation--%@",NSStringFromSelector(anInvocation.selector));
if ([BYMsgTool instancesRespondToSelector:anInvocation.selector]) {
//将方法的处理者改为mesTool
[anInvocation invokeWithTarget:mesTool];
}
}