一直以来,都知道object-c是支持消息转发的,也就是说它可以使一个类响应另外一个类中实现的消息(方法)。到底具体动态转发具体的过程是怎么样的.用代码说话.通过比较普通的方法调用和通过消息转发的方法调用来一探究竟.下面新建个简单的car类.
<pre>
@interface Car : NSObject
- (instancetype)car;
@property (nonatomic, copy) NSString *carInfo;
@end
</pre>
<pre>
@implementation Car
-(instancetype)init{
self = [super init];
if (self) {
_carInfo = @"This Is A Car";
}
return self;
}
+(instancetype)car{
return [[self alloc] init];
}
@end
</pre>
普通的方法调用
<pre>
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Car *myCar = [Car car];
NSString * upperStr = [(NSString *)myCar uppercaseString];
NSLog(@"%@--%@",myCar.carInfo,upperStr);
}
</pre>
- 普通的方法调用中,发送消息给一个无法处理该选择器(uppercaseString)的对象myCar,因为Car对象无法响应uppercaseString,且消息转发后也没有合适的对象响应uppercaseString(转发过程是怎么样的下面会说).所以程序奔溃了,错误提示是常见的
-[Car uppercaseString]: unrecognized selector sent to instance 0x7fe498cb3cd0
;通过下图,我们可以看出,-[NSObject(NSObject) doesNotRecognizeSelector:] + 205之前有forwarding + 970这条代码,这个应该就是消息转发了,消息转发之后仍然没有对象响应该方法,所以程序最后抛出unrecognized selector sent to instance 0x7fe498cb3cd0 的经典错误了.
实现消息转发功能的方法调用
在给程序添加消息转发功能以前,必须覆盖两个方法,即methodSignatureForSelector: 和 forwardInvocation:。
- methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名。
- forwardInvocation:将选择器转发给一个真正实现了该消息的对象.
在Car.m文件中重写上面两个方法
<pre>
//采用迭代的方式为当前被调用的方法创建一个有效的签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [self.carInfo methodSignatureForSelector:aSelector];
}
return signature;
}
//得到签名后,转入forwardInvocation:方法对其调用的方法(UTF8String)进行实现.
-(void)forwardInvocation:(NSInvocation *)anInvocation{
SEL aSelector = [anInvocation selector];
if ([self.carInfo respondsToSelector:aSelector]) {
[anInvocation invokeWithTarget:self.carInfo];
}
}
</pre>
同样调用方法:
<pre>
-(void)touchesBegan:(NSSet<UITouch *> )touches withEvent:(UIEvent )event{
Car *myCar = [Car car];
NSString *upperStr = [(NSString *)myCar uppercaseString];
NSLog(@"%@--%@",myCar.carInfo,upperStr);
}
</pre>
神奇的事情发生了:
- 具体是怎么实现的呢,self.carInfo是一个NSString对象,存在于Car类中.Car实例是无法正确的为另外一个对象(NSString)实现的选择器创建一个有效的签名。运行时当检查到当前没有有效的签名,即进入该对象(这里是myCar)的methodSignatureForSelector:方法中,此时,在这个方法中进行迭代并尝试构建一个有效的方法签名的机会.例如代码中,当myCar调用uppercaseString时,由于无法从当前对象中获得消息,转入第二次机会捕捉消息,首先进入methodSignatureForSelector:方法,采用迭代的方式为当前被调用的方法创建一个有效的签名,得到签名后,转入forwardInvocation:方法对其调用的方法(uppercaseString)进行实现. forwardInvocation:中,首先获得调用的方法(uppercaseString),判断self.carInfo(一个nsstring对象)能否响应该方法,如果可以,将调用uppercaseString对象的目标转换为self.carInfo对象. 这样 ,我们就实现了消息转发.