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

北城余情 提交于 2019-11-29 02:30:55

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
}

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:

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

    else {
        [target performSelector:selector];
    }

Hope this helps someone digging around.

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! :)

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

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];
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!