Получение параметров из универсального метода
Я пытаюсь получить параметры во время выполнения из некоторого случайного метода, который вызывается в моем классе. Перед 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 ответа:
Причина сбоя кода, скорее всего, заключается в том, что соглашение о вызове между 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
, и да, это немного проще.Надеюсь, это поможет!