iOS开发过程中,有一类的错误会经常遇到,就是找不到所调用的方法,当然这类问题比较好解决,给当前对象或其父类对象添加该方法即可,使得编译器在编译时能正确找到该方法;或者,还有另外的方法,由于Objective-C是一门动态语言,我们也可以在运行期再给类添加该方法,一样可以解决该问题,而这就涉及了类的消息转发机制。
消息转发到底是什么呢? 这里将分为三个部分进行逐一讲解:
1、动态方法解析
2、备用接收者
3、完整消息转发
截屏2020-03-0819.01.00.png
1.动态方法解析
首先,Objective-C运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。
如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"未实现的调用方法 = %@",NSStringFromSelector(sel));
IMP class = class_getMethodImplementation(self, @selector(functionOne));
class_addMethod(self, sel, class, "v@:");
return true;
}
-(void)functionOne{
NSLog(@"这是方法一");
}
我们可以在resolveInstanceMethod 中添加方法并返回YES,上例中我添加了functionOne并成功执行了,如果未添加方法实现,则返回false,切记不能在该方法中添加未实现的方法,否则会进入死循环。(实践中如果添加了方法,无论返回ture或者false都会执行添加的方法,不会进入消息转发,返回true或false似乎没什么影响,不过还是按照官方文档来做吧)。那么消息将进入到消息转发forwardingTargetForSelector中。
添加方法中应该看到了v@:,这是定义方法的,我会写一篇文章专门讲解这个。
2.备用接收者
如果我们在resolveInstanceMethod没有执行添加的方法,并且返回了false,那么消息将会转发到这里。
///消息转发,如果在这里转发只可转发给一个对象或一个方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"消息转发");
// return self.control;
return nil;
}
我们需要传递一个对象来接收这个方法信息。分为两种:
1.是传递方法的接收者,那么消息将传递给该对象并查找对应的方法,我们定义的方法名称和参数个数必须相同。返回值可以不同。
2.如果传为nil,那么消息将进入到完整消息转发中。
3.完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
消息转发分为两部分
//注册方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
/// 消息转发,可转发给多个对象,或者多个方法同时执行
- (void)forwardInvocation:(NSInvocation *)anInvocation;
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。
如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象
///注册方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [NSMethodSignature signatureWithObjCTypes:"#@:#"];
}
这里我定义的参数签名为"#@:#",接收一个NSString类型的参数,返回一个NSString类型的返回值。
/// 消息转发,可转发给多个对象,或者多个方法同时执行
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%@",anInvocation);
[anInvocation invokeWithTarget:self.control];
}
这里我们将消息转为控制器对象去接收处理。
NSString * value = [person performSelector:@selector(run:) withObject:@"这是参数"];
NSLog(@"返回值 = %@",value);
-(NSString * )run:(NSString * )value{
NSLog(@"转发给控制器调用 %@ %s",value,__FUNCTION__);
return @"这是返回值";
}
这里可以看到结果
RunTime-iOS[27980:1126582] 转发给控制器调用 这是参数 -[ViewController run:]
RunTime-iOS[27980:1126582] 返回值 = 这是返回值
控制器方法可以接收方法参数,也可以返回参数给最初的调用者。
网友评论