使用反射/内省调用参数数目未知的选择器

时间:2022-10-30 00:13:49

Lately I wrote an application in java (for android) which used reflection to invoke methods of some objects. The argument number and type was unknown, meaning, I had a unified mechanism that received an object name, method name and array of parameters (using JSON) and invoked the specified method on the specified object with an array of the arguments (Object[] filled with arguments of the required types).

最近我用java (android)编写了一个应用程序,它使用反射调用一些对象的方法。参数号和类型是未知的,也就是说,我有一个统一的机制,它接收一个对象名、方法名和参数数组(使用JSON),并用一个参数数组(对象[]填充所需类型的参数)在指定的对象上调用指定的方法。

Now I need to implement the same for iOS, I was able to invoke a selector when I knew the number of parameters the selector expected for like this:

现在我需要对iOS实现同样的操作,我可以调用一个选择器当我知道选择器期望的参数数量如下:

SEL selector = NSSelectorFromString(@"FooWithOneArg");
[view performSelectorInBackground:selector withObject:someArg];

I know I can get the number of arguments the selector receives by using

我知道我可以通过使用获取选择器接收到的参数数

int numberOfArguments = method_getNumberOfArguments(selector);

But is there a way to make a generic call like this:

但是,有没有一种方法可以像这样做:

[someObject performSelector:selector withObject:arrayOfObjects]

which is pretty much equivalent to Java's

这和Java差不多吗

someMethod.invoke(someObject, argumentsArray[]);

?

吗?

I want to avoid a switch case according to the amount of arguments the selector gets.

我希望避免根据选择器获得的参数数量来进行切换。

Sorry for the long dig, I just want to make my question as clear as possible.

抱歉这么长时间的挖掘,我只是想把我的问题说得越清楚越好。

4 个解决方案

#1


12  

This small function should do the trick, its not perfect, but it gives you a starting point:

这个小的函数应该做这个,它不是完美的,但是它给了你一个起点:

void invokeSelector(id object, SEL selector, NSArray *arguments)
{
    Method method = class_getInstanceMethod([object class], selector);
    int argumentCount = method_getNumberOfArguments(method);

    if(argumentCount > [arguments count])
        return; // Not enough arguments in the array

    NSMethodSignature *signature = [object methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:object];
    [invocation setSelector:selector];

    for(int i=0; i<[arguments count]; i++)
    {
        id arg = [arguments objectAtIndex:i];
        [invocation setArgument:&arg atIndex:i+2]; // The first two arguments are the hidden arguments self and _cmd
    }

    [invocation invoke]; // Invoke the selector
}

#2


3  

With the awesome help here including the simple but perfect answer from user102008 I pulled together the following example. Note what I was really trying to do was allow someone to send me a target selector that either did or did not take an argument. If it takes an argument I assume they want the calling object's "self" returned as a reference:

在这里有很棒的帮助,包括user102008的简单但完美的答案,我收集了以下示例。请注意,我真正想做的是允许某人向我发送一个目标选择器,这个选择器要么接受,要么不接受。如果它接受一个参数,我假设他们希望调用对象的“self”返回作为引用:

    NSMethodSignature * sig = [target methodSignatureForSelector:selector];
    if ([sig numberOfArguments] > 0) {
        [target performSelector:selector withObject:self];
    }

    else {
        [target performSelector:selector];
    }

Hope this helps someone digging around.

希望这能帮助别人挖掘。

#3


2  

I modified @JustSid answer and added more validation, nil argument support, changed it to an Obj-C NSObject category method, and add -performSelectorIfAvailable: helper methods for easier use. Please enjoy! :)

我修改了@JustSid的答案,并添加了更多的验证、nil参数支持,将它改为object - c NSObject类别方法,并添加了-performSelectorIfAvailable: helper方法,以便更容易使用。请享受!:)

#import <objc/runtime.h>

@implementation NSObject (performSelectorIfAvailable)

// Invokes a selector with an arbitrary number of arguments.
// Non responding selector or too few arguments will make this method do nothing.
// You can pass [NSNull null] objects for nil arguments.
- (void)invokeSelector:(SEL)selector arguments:(NSArray*)arguments {
    if (![self respondsToSelector:selector]) return; // selector not found

    // From -numberOfArguments doc,
    // "There are always at least 2 arguments, because an NSMethodSignature object includes the hidden arguments self and _cmd, which are the first two arguments passed to every method implementation."
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    int numSelArgs = [signature numberOfArguments] - 2;
    if (numSelArgs > [arguments count]) return; // not enough arguments in the array

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:selector];

    for(int i=0; i < numSelArgs; i++) {
        id arg = [arguments objectAtIndex:i];
        if (![arg isKindOfClass:[NSNull class]]) {
            [invocation setArgument:&arg atIndex:i + 2];
        }
    }
    [invocation invoke]; // Invoke the selector
}

#4


0  

Why not define each of your methods to take one argument: the array of objects? Presumably what you want is, with with the method

为什么不定义每个方法来接受一个参数:对象数组?大概你想要的是,用这个方法

-(void) doSomethingWithFoo:(id) foo andBar: (id) bar;

to invoke it with the parameters set from the array. Well, instead have:

用数组中设置的参数调用它。相反,有:

-(void) doSomethingWithArrayOfFooAndBar: (NSArray*) fooAndBar;

then your whole dispatch mechanism just becomes:

然后你的整个调度机制就变成:

[someObject performSelector:selector withObject:arrayOfObjects];

#1


12  

This small function should do the trick, its not perfect, but it gives you a starting point:

这个小的函数应该做这个,它不是完美的,但是它给了你一个起点:

void invokeSelector(id object, SEL selector, NSArray *arguments)
{
    Method method = class_getInstanceMethod([object class], selector);
    int argumentCount = method_getNumberOfArguments(method);

    if(argumentCount > [arguments count])
        return; // Not enough arguments in the array

    NSMethodSignature *signature = [object methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:object];
    [invocation setSelector:selector];

    for(int i=0; i<[arguments count]; i++)
    {
        id arg = [arguments objectAtIndex:i];
        [invocation setArgument:&arg atIndex:i+2]; // The first two arguments are the hidden arguments self and _cmd
    }

    [invocation invoke]; // Invoke the selector
}

#2


3  

With the awesome help here including the simple but perfect answer from user102008 I pulled together the following example. Note what I was really trying to do was allow someone to send me a target selector that either did or did not take an argument. If it takes an argument I assume they want the calling object's "self" returned as a reference:

在这里有很棒的帮助,包括user102008的简单但完美的答案,我收集了以下示例。请注意,我真正想做的是允许某人向我发送一个目标选择器,这个选择器要么接受,要么不接受。如果它接受一个参数,我假设他们希望调用对象的“self”返回作为引用:

    NSMethodSignature * sig = [target methodSignatureForSelector:selector];
    if ([sig numberOfArguments] > 0) {
        [target performSelector:selector withObject:self];
    }

    else {
        [target performSelector:selector];
    }

Hope this helps someone digging around.

希望这能帮助别人挖掘。

#3


2  

I modified @JustSid answer and added more validation, nil argument support, changed it to an Obj-C NSObject category method, and add -performSelectorIfAvailable: helper methods for easier use. Please enjoy! :)

我修改了@JustSid的答案,并添加了更多的验证、nil参数支持,将它改为object - c NSObject类别方法,并添加了-performSelectorIfAvailable: helper方法,以便更容易使用。请享受!:)

#import <objc/runtime.h>

@implementation NSObject (performSelectorIfAvailable)

// Invokes a selector with an arbitrary number of arguments.
// Non responding selector or too few arguments will make this method do nothing.
// You can pass [NSNull null] objects for nil arguments.
- (void)invokeSelector:(SEL)selector arguments:(NSArray*)arguments {
    if (![self respondsToSelector:selector]) return; // selector not found

    // From -numberOfArguments doc,
    // "There are always at least 2 arguments, because an NSMethodSignature object includes the hidden arguments self and _cmd, which are the first two arguments passed to every method implementation."
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    int numSelArgs = [signature numberOfArguments] - 2;
    if (numSelArgs > [arguments count]) return; // not enough arguments in the array

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:selector];

    for(int i=0; i < numSelArgs; i++) {
        id arg = [arguments objectAtIndex:i];
        if (![arg isKindOfClass:[NSNull class]]) {
            [invocation setArgument:&arg atIndex:i + 2];
        }
    }
    [invocation invoke]; // Invoke the selector
}

#4


0  

Why not define each of your methods to take one argument: the array of objects? Presumably what you want is, with with the method

为什么不定义每个方法来接受一个参数:对象数组?大概你想要的是,用这个方法

-(void) doSomethingWithFoo:(id) foo andBar: (id) bar;

to invoke it with the parameters set from the array. Well, instead have:

用数组中设置的参数调用它。相反,有:

-(void) doSomethingWithArrayOfFooAndBar: (NSArray*) fooAndBar;

then your whole dispatch mechanism just becomes:

然后你的整个调度机制就变成:

[someObject performSelector:selector withObject:arrayOfObjects];