2

I want to dynamically create the NSInvocation for the current method with the correct argument values. Typically, one might do this:

- (void)messageWithArg:(NSString *)arg arg2:(NSString *)arg2
{
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:_cmd]];
    [invocation setTarget:self];

    /*
     * How do we set the argument values here dynamically?
     */
}

Setting the argument values explicitly is trivial and we could do something like this:

[invocation setArgument:&arg atIndex:2];
[invocation setArgument:&arg2 atIndex:3];

I want to be able to do this in a loop with something like this:

for(int i = 0; i < [[self methodSignatureForSelector:_cmd] numberOfArguments]; i++) {
    [invocation setArgument:?!?! atIndex:i + 2];
}

The difficult part is dynamically getting the argument values for a given index.

A similar question was asked here where the answerer says he is not aware of a solution citing the complexity of the class. I disagree regarding the complexity - in the underlying code we already know exactly how the stack should look after the stack frame is set up because the compiler is aware of the calling convention used. For example, on x86 with stdcall we could easily access the argument values because we know they are a fixed offset from ebp:

  • Old ebp at 0(%ebp)
  • Return address at 4(%ebp)
  • First argument at 8(%ebp)
  • etc.

How can I achieve what I want or does there really not exist any mechanism in the language to support index-based fetching of argument values? At this point, I could accept this as true because no such feature exists in the C standard. However, I would like to get confirmation and/or an explanation of the reasoning behind this.

Community
  • 1
  • 1
Mike Kwan
  • 24,123
  • 12
  • 63
  • 96
  • Are all the arguments things like arg, arg2, arg3, arg4, etc... ? – Ramy Al Zuhouri Feb 01 '13 at 12:12
  • The signature will not be variadic and you can assume the arguments types consist of immutable objects or primitives. – Mike Kwan Feb 01 '13 at 12:17
  • Ok but the argument names, what are expected to be? If they're all numbered like arg3,arg4 that's easy to do. Even easier if you can put them in an array.Let me know. – Ramy Al Zuhouri Feb 01 '13 at 12:20
  • Just found another [relevant question](http://stackoverflow.com/questions/8022134/get-arguments-of-objective-c-method-by-index) where people are now saying it is possible but difficult. – Mike Kwan Feb 01 '13 at 12:20
  • @RamyAlZuhouri: The argument names are arbitrary and have no pattern. Also I do not wish to change the method signature. – Mike Kwan Feb 01 '13 at 12:20

1 Answers1

0

this works, but not what I was expected. va_start used in function with fixed arguments error stops me to use va_start in normal method. depends what you trying to achieve this may be useful.

@interface Test : NSObject

- (void)method:(id)arg1 :(id)arg2;

@end

@implementation Test

+ (void)load {
    class_addMethod([Test class], @selector(method::), (IMP)method_imp, "v@:@@");
}

void method_imp(id self, SEL _cmd, ...) {
    va_list ap;
    va_start(ap, _cmd);
    SEL sel = NSSelectorFromString([@"_" stringByAppendingString:NSStringFromSelector(_cmd)]);
    NSMethodSignature *signature = [self methodSignatureForSelector:sel];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    int argc = [signature numberOfArguments];
    char *ptr = (char *)ap;
    for (int i = 2; i < argc; i++) {
        const char *type = [signature getArgumentTypeAtIndex:i];
        [invocation setArgument:ptr atIndex:i];
        NSUInteger size;
        NSGetSizeAndAlignment(type, &size, NULL);
        ptr += size;
    }
    va_end(ap);
    [invocation setSelector:sel];
    [invocation invokeWithTarget:self];
}

- (void)_method:(id)arg1 :(id)arg2 {
    NSLog(@"%@, %@, %@", NSStringFromSelector(_cmd), arg1, arg2);
}

@end

call method:: will end up with _method:: and nothing is hardcoded

Test *test = [[Test alloc] init];
[test method:@"arg1" :@"arg2"];  // log: _method::, arg1, arg2
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • Thanks for the answer - this will certainly work for variadic methods but I already specified mine are not and can't be changed. – Mike Kwan Feb 01 '13 at 12:35
  • well, you still can try it. i use this code to make `IMP` function and add it to class at runtime, it works well for me. – Bryan Chen Feb 01 '13 at 12:38
  • I don't think this is portable. The calling convention for a (id, SEL, id, id) function may be different than for a (id, SEL, ...) function. The compiler constructs the method call assuming the method signature in the interface. If the actual function that receives it has a different calling convention, it will mess up. – newacct Feb 01 '13 at 19:46
  • I have tried this on simulator and iPad with different method signature, it works good. – Bryan Chen Feb 02 '13 at 00:42
  • Try `- (void)method:(char)arg1 :(char)arg2;` and signature `"v@:cc"` and method `- (void)_method:(char)arg1 :(char)arg2 { NSLog(@"%@, %d, %d", NSStringFromSelector(_cmd), arg1, arg2); }` and test code `Test *test = [[Test alloc] init]; [test method:1 :2];` It logs `_method::, 1, 0` on iOS simulator and `_method::, 16, 0` as a Mac executable. – newacct May 03 '13 at 01:01
  • @newacct ok you are right. but this still will work if the method signature is known at compile time. or generate one IMP function for each kind of method signature and choose which one to use base on `NSMethodSignature` – Bryan Chen May 07 '13 at 11:03