利用NSInvocation实现performSelector传递无限参数

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 - 
/**
 * 通过这个方法完成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传递无限参数)