美文网首页一个苹果
NSMethodSignature与NSInvocation

NSMethodSignature与NSInvocation

作者: _小沫 | 来源:发表于2018-02-11 15:45 被阅读7次

    NSMethodSignature与NSInvocation简单使用

    如果我们只知道一个方法的名称@"test:",那如何调用这个方法呢?
    我们的第一反应肯定是会想到通过 performSelector: withObject: 调用:

        SEL selector = NSSelectorFromString(@"test:");
        [instance performSelector:selector withObject:object];
    

    这一点问题也没有,但如果这个test方法是多参数的或者有返回值的,这时performSelector肯定是行不通的。这时我们就需要用到NSMethodSignature与NSInvocation了,直接贴代码:

    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self invocationMethod];
    }
    
    - (void)invocationMethod {
        // method1
        SEL selector1 = @selector(method1);
        NSMethodSignature *signature1 = [[self class] instanceMethodSignatureForSelector:selector1]; // selector ====> signature
        NSInvocation *invocation1 = [NSInvocation invocationWithMethodSignature:signature1]; // signature ====> invocation
        invocation1.target = self;
        invocation1.selector = selector1;
        [invocation1 invoke];
        
        // method2
        SEL selector2 = @selector(method2);
        NSMethodSignature *signature2 = [[self class] instanceMethodSignatureForSelector:selector2];
        NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:signature2];
        invocation2.target = self;
        invocation2.selector = selector2;
        [invocation2 invoke];
        NSString *value2 = @"";
        [invocation2 getReturnValue:&value2];
        NSLog(@"%@",value2);
        
        // method3
        SEL selector3 = @selector(method3:argument2:);
        NSMethodSignature *signature3 = [[self class] instanceMethodSignatureForSelector:selector3];
        NSInvocation *invocation3 = [NSInvocation invocationWithMethodSignature:signature3];
        invocation3.target = self;
        invocation3.selector = selector3;
        NSString *argument1 = @"argument1";
        NSString *argument2 = @"argument2";
        [invocation3 setArgument:&argument1 atIndex:2]; // 方法实际上有两个隐藏参数:self和_cmd 这里参数的index为2
        [invocation3 setArgument:&argument2 atIndex:3];
        [invocation3 invoke];
    }
    
    - (void)method1 {
        NSLog(@"method1");
    }
    
    - (NSString *)method2 {
        NSLog(@"method2");
        return @"method2 return";
    }
    
    - (void)method3:(NSString*)argument1 argument2:(NSString *)argument2 {
        NSLog(@"method3");
        NSLog(@"%@,%@",argument1,argument2);
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    实现起来会有点繁琐,但却很实用,运行的结果:

    2018-02-09 15:53:17.591 [4807:606671] method1
    2018-02-09 15:53:17.592 [4807:606671] method2
    2018-02-09 15:53:17.592 [4807:606671] method2 return
    2018-02-09 15:53:17.592 [4807:606671] method3
    2018-02-09 15:53:17.592 [4807:606671] argument1,argument2

    NSMethodSignature与NSInvocation应用场景

    消息转发

    • 将方法交由另一个类实现
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (nonatomic, strong) UILabel *label;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
        [self.view addSubview:_label];
        [self performSelector:@selector(setText:) withObject:@"viewController"];
    }
    
    // 重载methodSignatureForSelector:得到一个方法aSelector的签名,再由后面的forwardInvocation:去执行
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            signature = [_label methodSignatureForSelector:aSelector];
        }
        return signature;
    }
    
    // forwardInvocation:执行从methodSignatureForSelector:返回的NSMethodSignature,并将NSInvocation转发到其他对象
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL selector = [anInvocation selector];
        if ([_label respondsToSelector:selector]) {
            [anInvocation invokeWithTarget:_label];
        }
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    @end
    

    (UIViewController包含了UIlabel 属性 label, 如果UIViewController 实例调用setText:方法,由于类没有setText:方法,将会转发给 label 实现)


    UILabel文字设置成功
    • 向NSNull发送消息时导致崩溃问题解决
      对服务器返回的JSON数据解析时总会出现null数据导致崩溃的现象,避免这种崩溃出现的方法之一就是对返回的数据类型进行判断,但每个返回数据的地方都这么判断的话就会有点繁琐。另一个简单实用的方法就是消息转发:将向NSNull发送的消息转发给可以执行该方法的类。这里通过创建一个NSNull的分类就能实现:
    #import "NSNull+signature.h"
    
    #define nullObjects @[@"",@0,@[],@{}] // 执行相关方法的数据类型
    
    @implementation NSNull (signature)
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
        if (!signature) {
            for (NSObject *object in nullObjects) {
                signature = [object methodSignatureForSelector:aSelector];
                if (signature) {
                    break;
                }
            }
        }
        return signature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        SEL selector = [anInvocation selector];
        for (NSObject *object in nullObjects) {
            if ([object respondsToSelector:selector]) {
                [anInvocation invokeWithTarget:object];
            }
        }
        
    //    [self doesNotRecognizeSelector:selector]; // 抛出异常
    }
    
    @end
    
    #import "ViewController.h"
    #import "NSNull+signature.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        id nullObject = [NSNull null];
        NSString *nullStr = nullObject;
        NSUInteger strLength = nullStr.length;
        NSLog(@"%lu",(unsigned long)strLength);
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    不加分类运行后会抛出以下异常:

    -[NSNull length]: unrecognized selector sent to instance 0x195c466e0

    加分类后一切正常,输出结果:

    2018-02-11 15:41:01.528 [7186:716497] 0

    通过消息转发我们很容易就解决了NSNull崩溃的问题,再也不用担心解析服务器数据为null的情况了。

    对于消息转发这里只做了简单的应用,想要了解关于消息转发的更多信息可以参考以下两篇博客:
    函数调用
    Runtime 你为何如此之屌?

    相关文章

      网友评论

        本文标题:NSMethodSignature与NSInvocation

        本文链接:https://www.haomeiwen.com/subject/qyzftftx.html