iOS runtime 消息转发

来源:互联网 发布:鸟哥linux基础篇 编辑:程序博客网 时间:2024/06/05 05:34

作者:Love@YR
链接:http://blog.csdn.net/jingqiu880905/article/details/50897011
请尊重原创,谢谢!

首先看个图:(抠自:http://www.cocoachina.com/ios/20160301/15494.html)
这里写图片描述
文章参考:http://blog.csdn.net/hyugahinat/article/details/50688333
还有:http://blog.csdn.net/i_am_jojo/article/details/44704633
下面看一下测试的代码:

ViewController.m中:

////  ViewController.m//  TestRuntime//#import "ViewController.h"#import <objc/runtime.h>@interface ViewController ()@endIMP orginIMP;//全局函数NSString * MyUppercaseString(id SELF, SEL _cmd){    NSLog(@"begin uppercaseString");    NSString *str = orginIMP(SELF,_cmd);    //若此处出现 too many arguments to function call的错误,把build setting 里的enable strict checking of objc_msgSend calls改为No    NSLog(@"end uppercaseString");    return str;}void dynamicMethodIMP (id self,SEL _cmd, NSString *str){    NSLog(@"do something sel, argument is %@",str);}@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    [self performSelector:@selector(doSomething:) withObject:@"hi,how are you?"];    [self testReplaceMethod];     [self performSelector:@selector(secondVCMethod:withBoolValue:) withObject:[NSNumber numberWithInteger:10] withObject:[NSNumber numberWithBool:NO]];}//第一个机会:动态添加一个实例方法给本类,类方法的话用的是resolveClassMethod+ (BOOL)resolveInstanceMethod:(SEL)sel {    if (sel == @selector(doSomething:)) {        NSLog(@"add method here");        class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:@"); //双击选中class_addMethod方法,xcode的导航栏右侧出现quick help,点击parameters里的Type encodings进入苹果开发文档可以查阅"v@:"代表的意思         return YES;    }    return [super resolveInstanceMethod:sel];}//方法替换,(替换系统的方法)-(void)testReplaceMethod{    Class strcls = [NSString class];    SEL  oriUppercaseString = @selector(uppercaseString);    orginIMP = [NSString instanceMethodForSelector:oriUppercaseString];    IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);    NSString *s = @"hello world";    NSLog(@"%@",[s uppercaseString]);}//第二个机会,替换消息的接受者为其他对象//返回参数是一个对象,如果这个对象非nil、非self的话,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。- (id)forwardingTargetForSelector:(SEL)aSelector {    Class class = NSClassFromString(@"SecondViewController");    UIViewController *vc = class.new;    //下面3种if都行    if ([vc respondsToSelector:aSelector]) {     //if (aSelector==NSSelectorFromString(@"secondVCMethod:withBoolValue:")) {    //if (aSelector==@selector(secondVCMethod:withBoolValue:)) {        NSLog(@"sencondvc do this!");        return vc;    }    return nil;}//第三个机会,当第二个返回nil时执行//这个函数和后面的forwardInvocation:是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行//真正执行从methodSignatureForSelector:返回的NSMethodSignature。在这个函数里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(forwardingTargetForSelector只能以Selector的形式转向一个对象)- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{    Class class = NSClassFromString(@"SecondViewController");    UIViewController *vc = class.new;    if (aSelector==NSSelectorFromString(@"secondVCMethod:withBoolValue:")) {        NSMethodSignature *signature=[super methodSignatureForSelector:aSelector];        if (!signature) {            signature=[vc methodSignatureForSelector:aSelector];             NSUInteger numberOfArguments = signature.numberOfArguments;            for (NSUInteger i = 2; i < numberOfArguments; i++) {//secondVCMethod:withBoolValue:的方法, signature的return type 为v,参数为@:qB,所以从第3个字母开始才是参数的真正类型.     const char *argumentType = [signature getArgumentTypeAtIndex:i];        NSLog(@"%c",argumentType[0]);               }        }        return signature;    }    return nil;}//该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。//这里需要注意的是参数anInvocation是从哪的来的呢?其实在forwardInvocation:消息发送前,Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。所以我们在重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法,否则会抛异常。- (void)forwardInvocation:(NSInvocation *)anInvocation{    Class class = NSClassFromString(@"SecondViewController");    UIViewController *vc = class.new;    if ([anInvocation selector]==NSSelectorFromString(@"secondVCMethod:withBoolValue:")) {        if ([vc respondsToSelector:             [anInvocation selector]])            [anInvocation invokeWithTarget:vc];        else            [super forwardInvocation:anInvocation];    }}- (void)doesNotRecognizeSelector:(SEL)aSelector{    NSLog(@"throw a exception");}- (void)didReceiveMemoryWarning {    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.}@end

其中secondVCMethod方法被转发给SecondViewController,实现如下:

-(void)secondVCMethod:(NSInteger)number withBoolValue:(BOOL)bovalue{    NSString *str1=[NSString stringWithFormat:@"This is secondVC method!,number is %@, and bo value is %@",@(number),@(bovalue)];    NSLog(@"str1");}

上述代码对此图做了说明
首先ViewController里找不到secondVCMethod方法,会自动调用resolveInstanceMethod,如果返回不是yes则会调用forwardingTargetForSelector,如果此方法返回的为nil,则会调用methodSignatureForSelector,此方法返回nil的话直接走到doesNotRecognizeSelector抛异常,此方法不为nil的话调用forwardInvocation

而class_addMethod class_replaceMethod 方法则是动态的添加一个方法或者替换一个方法的实现。
根据Demo实验,resolveInstanceMethod函数返回的BOOL值系统实现的objc_msgSend函数并没有参考,无论返回什么系统都会尝试再次用SEL找IML,如果找到函数实现则执行函数。如果找不到继续其他查找流程。所以会发现调用两次。

0 0
原创粉丝点击