Получение параметров из универсального метода


Я пытаюсь получить параметры во время выполнения из некоторого случайного метода, который вызывается в моем классе. Перед arm64 (на armv7 и armv7s) Это можно сделать следующим кодом:

@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). После второго call it's stopped with EXC_BAD_ACCESS. Поэтому я думаю, что он даже не нашел первый параметр (с va_start(arguments, obj1)).

Также обратите внимание, что функциональность va_list отлично работает на arm64 в случае, если я вызываю dynamicMethod: напрямую (и вручную устанавливаю количество аргументов). Мое дикое предположение, что он не работает из-за неправильного кодирования метода (он не волшебным образом преобразует один метод в другой с различным количеством параметров на arm64, как это было раньше).

Вы можете посмотреть весь код здесь , это в основном веб-сервис часть этого решения.

3 4

3 ответа:

Причина сбоя кода, скорее всего, заключается в том, что соглашение о вызове между arm (32bit) и arm64 отличается. Иными словами, применяются различные правила относительно того, как параметры передаются в функцию и как возвращаются значения.

Раньше не было никакого "волшебного превращения". Вам повезло, что соглашение о вызове для вариадических функций было таким же, как и для не-вариадических - по крайней мере, в ваших случаях использования.

Смотрите разделы передача параметров как в стандарте вызова процедуры ARM для arm64 , так и в стандарте вызова процедуры ARM (не 64 бит).

Удачи в решении этой задачи; вероятно, вам придется иметь два отдельных пути кода.

EDIT

Я считаю, что "правильный" способ достичь того, что вы ищете, - это реализовать ряд функций со всеми возможными перестановками аргументов, которые вы ожидаете обработать, и разрешить их динамически на основе сигнатуры селектора. JSCocoa делает это с использованием того, что они называют "бассейн Беркса" (я полагаю, названный в честь Тима Беркса )

Также проверьте libffi для iOS: https://github.com/roupam/Objective-C-NuREPL-for-iOS/tree/master/Remote/libffi

Наконец, связанный пост: -[NSInvocation getReturnValue:] с двойным значением неожиданно порождает 0

Неожиданно я получил приличное решение от PR на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];
}

Так просто и все еще так мощно. Работает для любой архитектуры процессора, потому что это высокоуровневое решение. Я виню себя, что сам не нашел его=)

A нашел другой способ динамического вызова функции. Взгляните на этот фрагмент кода:

- (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 :

Этот класс не сохраняет аргументы для содержащегося вызова по умолчанию. Если эти объекты могут исчезнуть между временем, которое вы создайте свой экземпляр NSInvocation и в то время, когда вы его используете, вы следует явно сохранить объекты самостоятельно или вызвать retainArguments метод, чтобы иметь вызов объект сохраняет их сам.

Кроме того, аргументы, которые вы передаете, получат индексы 2 или больше, см. почему: (та же ссылка выше)

Индексы 0 и 1 указывают на скрытые аргументы self и _cmd, соответственно; эти значения могут быть получены непосредственно с целью и методы селекции. Используйте индексы 2 и выше для Аргументов обычно передается в сообщении.

Для завершенного запуска exmaple, если вы уже добавили код приглашения друга facebook в эта нить.

Этот код динамически вызывает функцию делегата, считывая ее из интерфейса делегата и вызывая с аргументами / без аргументов. Идея этого решения заключается в том, что оно не требует какой-либо внешней ссылки на кодировку, плюс вы должны сохранить свой _callback, чтобы избежать EXC_BAD_ACCESS, и да, это немного проще.

Надеюсь, это поможет!