Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Receiving parameters from a generic method

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.

like image 682
Ossir Avatar asked Dec 19 '22 16:12

Ossir


2 Answers

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=)

like image 32
Ossir Avatar answered Dec 22 '22 04:12

Ossir


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

like image 83
TomSwift Avatar answered Dec 22 '22 06:12

TomSwift