从通用方法接收参数

时间:2014-04-02 14:55:36

标签: ios objective-c reflection variadic-functions

我正在尝试从我的类调用的一些随机方法接收运行时的参数。在arm64之前(armv7armv7s),可以使用以下代码完成:

@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;
}

这很简单干净。我们只是将所有传入调用传递给一个可以从中收集参数并返回它们的实现。

但是在arm64中,va_list效果很好,但在这种情况下,va_arg(arguments, id)中的第一个参数是类(self)的当前实例。第二次通话后,EXC_BAD_ACCESS停止了。所以我认为它甚至没有找到第一个参数(va_start(arguments, obj1))。

另请注意,如果我直接调用arm64(并手动设置参数数量),va_list功能在dynamicMethod:上正常工作。我猜测它因为错误的方法编码而无法正常工作(它不会像以前那样神奇地将一个方法转换为arm64上具有不同数量参数的另一个方法。)

您可以查看所有代码here,它基本上是this solution的网络服务部分。

3 个答案:

答案 0 :(得分:7)

您的代码失败的原因可能是因为arm(32位)和arm64之间的调用约定不同。也就是说,关于如何将参数传递给函数以及如何返回值,将应用不同的规则。

没有&#34;魔术转换&#34;继续前进你很幸运,可变函数的调用约定与非可变函数的调用约定相同 - 至少在你的用例中是这样。

请参阅ARM Procedure Call Standard for arm64ARM Procedure Call Standard(非64位)中的参数传递部分。

祝你好运解决这个问题;你可能需要有两个独立的代码路径。

修改

我相信&#34;正确&#34;实现你所追求的目标的方法是使用你希望处理的所有可能的参数排列来实现许多函数,并根据选择器签名动态地解析这些函数。 JSCocoa使用他们称为"Burks Pool"的内容(我认为以Tim Burks命名)

另请查看适用于iOS的libffi:https://github.com/roupam/Objective-C-NuREPL-for-iOS/tree/master/Remote/libffi

最后,相关帖子:-[NSInvocation getReturnValue:] with double value produces 0 unexpectedly

答案 1 :(得分:4)

出乎意料的是,我在Github上获得了公关的体面解决方案,所以所有学分都转到@sandor-gazdag。这是解决方案:

- (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];
}

如此简单,仍然如此强大。适用于任何处理器架构,因为它是高级解决方案。我责备自己,我自己也找不到它=)

答案 2 :(得分:0)

找到另一种动态调用函数的方法。看看这段代码:

- (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];
}

来自NSInvocation document的部分:

  

此类不保留包含的调用的参数   默认情况下。如果那些物体可能在你之间消失   创建你的NSInvocation实例和你使用它的时间,你   应该自己明确地保留对象或调用   retainArguments方法让调用对象保留它们   本身。

此外,您传递的参数将获得索引2或更高,请参阅原因:(上述链接相同)

  

索引0和1表示隐藏的参数self和_cmd,   分别;可以使用目标直接检索这些值   和选择器方法。使用索引2和更大的参数   通常传递给消息。

对于已完成的正在运行的例程,如果已在this thread中添加了facebook邀请朋友代码。

此代码通过从委托的接口读取委托函数并使用/不使用参数调用来动态调用委托函数。

这个解决方案背后的想法是,它不需要任何外部参考编码,另外你应该保留_callback以避免EXC_BAD_ACCESS,是的,这有点简单。

希望它有所帮助!