美文网首页
利用NSInvocation实现performSelector传

利用NSInvocation实现performSelector传

作者: 大虾咪 | 来源:发表于2017-09-06 17:54 被阅读164次

    NSInvocation的应用(UIWebView与JS交互传递3个以上参数)

    前言

    在iOS开发中,可以直接调用方法的方式有两种:performSelector:withObject: 和 NSInvocation。
    performSelector:withObject:使用简单,但缺点是只能传一个参数,大于2个参数就无法使用;NSInvocation就不一样,功能更加强大

    NSInvocation是一个消息调用类,它包含了所有OC消息的成分:target、selector、参数以及返回值。NSInvocation可以将消息转换成一个对象,消息的每一个参数能够直接设定,而且当一个NSInvocation对象调度时返回值是可以自己设定的。一个NSInvocation对象能够重复的调度不同的目标(target),而且它的selector也能够设置为另外一个方法签名。NSInvocation遵守NSCoding协议,但是仅支持NSPortCoder编码,不支持归档型操作。

    提示:在面向对象中,方法也称消息。

    小试牛刀

    使用步骤
    • 根据方法创建签名对象(NSMethodSignature对象)

    • 根据签名对象创建调用对象(NSInvocation对象)

    • 设置调用对象(NSInvocation对象)的相关信息

    • 调用方法

    • 获取方法返回值

    1. 调用有参无返回值的方法
    //调用有参无返回值的方法
    - (void)invocation2 {
    
        // 1. 根据方法创建签名对象sig
        NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:@selector(methodWithArg1:arg2:)];
    
        // 2. 根据签名对象创建调用对象invocation
        NSInvocation *invocation =[NSInvocation invocationWithMethodSignature:sig];
    
        // 3. 设置调用对象的相关信息
        invocation.target = self;
        invocation.selector = @selector(methodWithArg1:arg2:);
        NSString *name = @"SJM";
        int age = 18;
        // 参数必须从第2个索引开始,因为前两个已经被target和selector使用
        [invocation setArgument:&name atIndex:2];
        [invocation setArgument:&age atIndex:3];
    
        // 4. 调用方法
        [invocation invoke];
    
    }
    
    1. 调用有参有返回值的方法
    - (void)invocation3 {
    
        // 1. 根据方法创建签名对象sig
        NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:@selector(methodWithArg1:arg2:arg3:)];
    
        // 2. 根据签名对象创建调用对象invocation
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
    
        // 3. 设置调用对象的相关信息
        invocation.target = self;
        invocation.selector = @selector(methodWithArg1:arg2:arg3:);
        int value1 = 111;
        int value2 = 999;
        int value3 = 666;
        [invocation setArgument:&value1 atIndex:2];
        [invocation setArgument:&value2 atIndex:3];
        [invocation setArgument:&value3 atIndex:4];
    
        // 4. 调用方法
        [invocation invoke];
    
        // 5. 获取方法返回值
        NSNumber *num = nil;
        [invocation getReturnValue:&num];
        NSLog(@"最大数为:%@",num);
    
    }
    
    - (NSNumber *)methodWithArg1:(int)arg1 arg2:(int)arg2 arg3:(int)arg3 {
    
        int max1 = MAX(arg1, arg2);
        int max2 = MAX(arg2, arg3);
    
        // 返回最大数
        return @(MAX(max1, max2));
    }
    

    总结

    1. 可以利用上边的例子接和官方demo 对NSObject写个分类
      2.js和UIwebView交互的时候可以随心所欲传递N个参数
    #pragma mark - <UIWebViewDelegate>
    /**
     * 通过这个方法完成JS调用OC
     * JS和OC交互的第三方框架:WebViewJavaScriptBridge
     */
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
    {
        // url == xmg://sendMessage_?200
        NSString *url = request.URL.absoluteString;
        NSString *scheme = @"xmg://";
        if ([url hasPrefix:scheme]) {
            // 获得协议后面的路径  path == sendMessage_number2_?200&300
            NSString *path = [url substringFromIndex:scheme.length];
            // 利用?切割路径
            NSArray *subpaths = [path componentsSeparatedByString:@"?"];
            // 方法名 methodName == sendMessage:number2:
            NSString *methodName = [[subpaths firstObject] stringByReplacingOccurrencesOfString:@"_" withString:@":"];
            // 参数  200&300
            NSArray *params = nil;
            if (subpaths.count == 2) {
                params = [[subpaths lastObject] componentsSeparatedByString:@"&"];
            }
            // 调用
            [self performSelector:NSSelectorFromString(methodName) withObjects:params];
            
            return NO;
        }
        
        NSLog(@"想加载其他请求,不是想调用OC的方法");
        
        return YES;
    }
    
    #import "NSObject+MFInvocation.h"
    
    @implementation NSObject (MFInvocation)
    
    
    - (id)performSelector:(SEL)aSelector withArguments:(NSArray *)arguments {
        
    //    if (aSelector == nil) return nil;
        NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];
        if (signature == nil ) {
            [NSException raise:@"牛逼的错误" format:@"%@方法找不到", NSStringFromSelector(aSelector)];
            //生产环境根据情况选择注销   当aSelector为空的时候会崩溃到这里
        }
    
        // NSInvocation : 利用一个NSInvocation对象包装一次方法调用(方法调用者、方法名、方法参数、方法返回值)
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        invocation.target = self;
        invocation.selector = aSelector;
        
        // 设置参数 invocation 有2个隐藏参数,所以 argument 从2开始
        // 除self、_cmd以外的参数个数
        if ([arguments isKindOfClass:[NSArray class]]) {
            NSInteger count = MIN(arguments.count, signature.numberOfArguments - 2);
            for (int i = 0; i < count; i++) {
                
                id object = arguments[i];
                if ([object isKindOfClass:[NSNull class]]) continue;
    
                const char *type = [signature getArgumentTypeAtIndex:2 + i];
                
                // 需要做参数类型判断然后解析成对应类型,这里默认所有参数均为OC对象
                if (strcmp(type, "@") == 0) {
                    id argument = arguments[i];
                    [invocation setArgument:&argument atIndex:2 + i];
                }
            }
        }
        //调用方法
        [invocation invoke];
        // 获取返回值
        id returnVal = nil;
    //    if (signature.methodReturnLength) { // 有返回值类型,才去获得返回值
    //        [invocation getReturnValue:&returnVal];
    //    }
        if (strcmp(signature.methodReturnType, "@") == 0) {
            [invocation getReturnValue:&returnVal];
        }
        // 需要做返回类型判断。比如返回值为常量需要包装成对象,这里仅以最简单的`@`为例
        return returnVal;
    }
    @end
    

    相关文章

      网友评论

          本文标题:利用NSInvocation实现performSelector传

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