Question

I'm trying to receive parameters in runtime from some random method that is invoked on my class. Before arm64 (on armv7 and armv7s) it can be done with following code:

@interface MyClass
// It does not matter what method, we declare it for compiler only
- (id)methodWithFirstParameter:(id)firstParam secondParameter:(id)secondParam;
@end

@implementation MyClass

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    [self addDynamicCallForSelector:sel];
    return YES;
}

+ (void)addDynamicCallForSelector:(const SEL)selector {
    const char *encoding;
    IMP implementation;
    implementation = [self instanceMethodForSelector:@selector(dynamicMethod:)];
    Method newMethod = class_getInstanceMethod([self class], @selector(dynamicMethod:));
    encoding = method_getTypeEncoding(newMethod);
    class_addMethod([self class], selector, implementation, encoding);
}

- (id)dynamicMethod:(id)obj1, ... {
    int parameterCount = [[NSStringFromSelector(_cmd) componentsSeparatedByString:@":"] count] - 1;
    NSMutableArray *parameterList = [[NSMutableArray alloc] initWithCapacity:parameterCount];
    va_list arguments;
    va_start(arguments, obj1);
    for (int i = 0; i < parameterCount; i++) {
        id parameter = (i == 0) ? obj1 : va_arg(arguments, id);
        if (!parameter) {
            parameter = [NSNull null];
        }
        [parameterList addObject:parameter];
    }
    va_end(arguments);
    return parameterList;
}

It's pretty easy and clean. We just pass all incoming invocation to one single implementation that can gather parameters from it and return them.

In arm64 however, va_list works good, but in such context, the first parameter from va_arg(arguments, id) is current instance of class (self). After second call it's stopped with EXC_BAD_ACCESS. So I think it did not even find first parameter (with va_start(arguments, obj1)).

Also notice that va_list functionality works fine on arm64 in case I invoke dynamicMethod: directly (and manually set number of arguments). My wild guess that it does not work because of wrong method encoding (it does not magically convert one method into another with different number of parameters on arm64 like it was before).

You can look all code here, it's basically web service part of this solution.

Was it helpful?

Solution 2

Unexpectedly, I've got decent solution from PR on Github so all credits go to @sandor-gazdag. Here's solution:

- (void)forwardInvocation:(NSInvocation *)inv {
    NSUInteger n = [[inv methodSignature] numberOfArguments];

    NSMutableArray *parameterList = [[NSMutableArray alloc] init];
    for (NSUInteger i = 0; i < n - 2; i++) {
        id __unsafe_unretained arg;
        [inv getArgument:&arg atIndex:(int)(i + 2)];
        if (!arg) {
            arg = [NSNull null];
        }
        [parameterList addObject:arg];
    }
    [self dynamicWebServiceCallWithArguments:parameterList forInvocation:inv];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSUInteger numArgs = [[NSStringFromSelector(aSelector) componentsSeparatedByString:@":"] count] - 1;
    return [NSMethodSignature signatureWithObjCTypes:[[@"@@:@" stringByPaddingToLength:numArgs + 3 withString:@"@" startingAtIndex:0] UTF8String]];
}

- (void)dynamicWebServiceCallWithArguments:(NSMutableArray *)parameterList forInvocation:(NSInvocation *)invocation {
   ... 
   id result = [self executeDynamicInstanceMethodForSelector:invocation.selector parameters:parameterList prepareToLoadBlock:prepareToLoadBlock success:successBlock failure:failureBlock];
   [invocation setReturnValue:&result];
}

So simple and still so powerful. Works for any processor architecture, because it's high-level solution. I blame myself that I've not find it myself=)

OTHER TIPS

The reason your code is failing is likely because the calling convention between arm (32bit) and arm64 is different. That is to say, different rules are being applied as to how parameters are passed to the function, and how values are returned.

There was no "magic conversion" going on before. You got lucky that the calling convention for variadic functions was the same as for non-variadic - at least in your use cases.

See the Parameter Passing sections in both the ARM Procedure Call Standard for arm64 and ARM Procedure Call Standard (non-64 bit).

Good luck solving this; you'll likely have to have two separate code paths.

EDIT

I believe the "correct" way to achieve what you're after is to implement a number of functions with all of the possible permutations of arguments you expect to handle, and resolve to those dynamically based on the selector signature. JSCocoa does this using what they call a "Burks Pool" (I believe named for Tim Burks)

Also check out libffi for iOS: https://github.com/roupam/Objective-C-NuREPL-for-iOS/tree/master/Remote/libffi

Lastly, a related post: -[NSInvocation getReturnValue:] with double value produces 0 unexpectedly

A found another way of dynamically invoking of a function. Have a look at this piece of code:

- (void)requestSucceeded 
{
    NSLog(@"requestSucceeded");
    id owner = [fbDelegate class];
    SEL selector = NSSelectorFromString(@"OnFBSuccess");
    NSMethodSignature *sig = [owner instanceMethodSignatureForSelector:selector];
    _callback = [NSInvocation invocationWithMethodSignature:sig];
    [_callback setTarget:owner];
    [_callback setSelector:selector];
    [_callback retain];       // <------ See the partial doc attached

    [_callback invokeWithTarget:fbDelegate];
}

A part from NSInvocation document:

This class does not retain the arguments for the contained invocation by default. If those objects might disappear between the time you create your instance of NSInvocation and the time you use it, you should explicitly retain the objects yourself or invoke the retainArguments method to have the invocation object retain them itself.

Also, the arguments you passes would get indices 2 or greater, see why: (same above link)

Indices 0 and 1 indicate the hidden arguments self and _cmd, respectively; these values can be retrieved directly with the target and selector methods. Use indices 2 and greater for the arguments normally passed in a message.

For a completed running exmaple, if have already added a facebook invite friend code in this thread.

This code dynamically calls the delegate function by reading it from the delegate's interface and invoked with/without arguments.

The idea behind this solution is that it does not required any external reference of Encoding, plus you should retain your _callback to avoid EXC_BAD_ACCESS and yes it is a bit simpler.

Hope it helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top