Calling a selector with an unknown number of arguments using reflection / introspection

Recently, I wrote an application in java (for android) that used reflection to call methods on some objects. The argument number and type were unknown, that is, I had a single mechanism that got the name of the object, the name of the method and an array of parameters (using JSON) and called the specified method on the specified object with an array of arguments (Object [] filled with arguments of the required types).

Now I need to implement the same for iOS, I was able to call the selector when I knew the number of parameters that such a selector expected:

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

I know that I can get the number of arguments the selector gets 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

someMethod.invoke(someObject, argumentsArray[]);

?

, .

, .

+5
4

, , :

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
}
+12

, , user102008, . , , - , , . , , , "" :

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

    else {
        [target performSelector:selector];
    }

, - .

+3

@JustSid , nil, Obj-C NSObject -performSelectorIfAvailable: . , !:)

#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
}
+2

Why not define each of your methods to accept one argument: an array of objects? Presumably what you want using the method

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

to call it with the parameters given from the array. Well, instead:

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

then your whole sending mechanism will just become:

[someObject performSelector:selector withObject:arrayOfObjects];
0
source

All Articles