一、片头
在Objective-C中,方法调用可以说成是消息发送。向一个对象发送任意一条消息都是可以的,即使类中没有实现该消息(方法)。
比如下面的rev的类中没有willCrash这个方法,向对象rev发送消息
[rev performSelector:@selector(willCrash)]
或
objc_msgSend(rev,@selector(willCrash))
当执行到上面的这句话时,显然会发生崩溃。
(抛出 unrecognized selector sent to instance。)
好在,像这种情况,苹果贴心地提供了一种“补全”机制,
也叫“消息转发”机制,可以让编程者有机会处理崩溃。
消息转发,通俗来讲就是找人背锅。主要走的流程,是重写NSObject的三个方法:
1、自己试下补锅:+resolveInstanceMethod:(要处理静态方法就是+resolveClassMethod:)
2、让别人来背锅:-forwardingTargetForSelector:
3、还是自己解决吧:-(void)forwardInvocation:(NSInvocation *)anInvocation
二、详解
举个例,Person类,类中只有-run方法
// Person.h
@interface Person : NSObject
- (void)run;
@end
// Person.m
@implememtation
- (void)run {
NSLog(@"run");
}
@end
向这个类对象调用一个不存在的方法fly
Person *man = [Person new];
[man performSelector:@selector(fly)]; // fly并没有相应实现
下面来看看,怎么用“消息转发”,从而让它不崩溃。
1、resolveInstanceMethod(自己试下补锅)
首先来重写Person类的-resolveInstanceMethod:方法,打个断点,可以看到在程序挂掉之前该方法被回调
+ (BOOL)resolveInstanceMethod:(SEL)sel {
BOOL didResolve;
// do something ..
return didResolve;
}
- 无论该方法return YES或者NO,都不影响转发行为。虽然不清楚return的作用,反正结果就是没什么卵用
- 总之在这个方法结束之前,动态实现fly方法。下面是让@selector(fly)指向@selector(getTwoWings)的实现(其实可以这么理解,@selector(fly)和@selector(getTwoWings)都是一个符号,对应指向的都是同一个实现)
@implememtation
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel==@selector(fly)) NSLog(@"try fly");
// 获得方法对象
Method impMethod = class_getInstanceMethod(self, @selector(getTwoWings));
// 为fly方法添加getTwoWings的实现
class_addMethod(self, @selector(fly), method_getImplementation(impMethod), method_getTypeEncoding(impMethod));
// return YES NO都一样
return YES;
}
- (void) getTwoWings {
NSLog(@"get two wings and i will fly");
}
@end
// 不会发生崩溃,控制台打印 try fly
// 控制台打印 get two wings and i will fly
[man performSelector:@selector(fly)];
如果不给fly动态实现,接下来会进入“转发”
2、forwardingTargetForSelector(让别人来背锅)
“转发”会回调forwardingTargetForSelector:
把该消息转发给某个对象来处理:
- (id)forwardingTargetForSelector:(SEL)aSelector {
id handler; // 可以处理该消息的对象
return handler;
}
- 若return为nil,不转发消息,后续仍然是自己来处理
- 若return不为nil,由对象handler去回调处理这个消息。也就是相当于
[hander performSelector:@selector(fly)];
// 实现上是 IMP ptr = class_getMethodImplementation([handler class], @selector(fly));
// if ptr == NULL {让handler处理,转发到此结束}else{还有下一步}
3、forwardInvocation(还是自己解决吧)
回调Person的-(void)forwardInvocation:(NSInvocation *)anInvocation
-(void)forwardInvocation:(NSInvocation *)anInvocation {
if (anInvocation.selector==@selector(fly)) {
// 相应处理,消息转发完成
}
}
三、测试例子
实现以下的场景:
向person发送fly消息,实际上让另一个对象去调用它的fly方法
重写-forwardingTargetForSelector:,把fly消息转发给Plane类对象去处理
// Person类
@implememtation
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel==@selector(fly)) NSLog(@"try fly");// 没有实现-fly,那么消息将转发
return NO;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
Plane *plane= [Plane new];
return plane;// 转发给plane对象
}
@end
Plane是另外一个类:
// - Plane.h
@interface Plane : NSObject
@end
// - Plane.m
@implememtaion Plane
-(void)fly {
NSLog(@"take a plane and i will fly");
}
@end
测试结果
Person *man = [Person new];
[man performSelector:@selector(fly)];
// 控制台打印 try fly
// 控制台打印 take a plane and i will fly
相当于调用了[plane fly];
片尾
当向一个对象发送一个它不能处理的消息/方法,苹果会不断地询问你,去动态实现这个方法,简而言之,消息转发就是这么一回事。
实际应用上,消息转发配合上Runtime也有很多玩法。比如。。就不比如了,哪天写一篇文章,先挖个坑吧~
再至于流程3的forwardInvocation中,NSInvocation怎么个用法?请看另外下面这篇文章,拉到最底就能看到了
NSInvocation:iOS 不走寻常路:调用方法的6种姿势,你知道几种
网友评论