Вызов селектора с неизвестным количеством аргументов с помощью отражения / самоанализа - PullRequest
5 голосов
/ 26 апреля 2011

В последнее время я написал приложение на Java (для Android), которое использовало отражение для вызова методов некоторых объектов. Номер и тип аргумента были неизвестны, то есть у меня был единый механизм, который получал имя объекта, имя метода и массив параметров (используя JSON) и вызывал указанный метод для указанного объекта с массивом аргументов (Object [] заполнены аргументами обязательных типов).

Теперь мне нужно реализовать то же самое для iOS, я смог вызвать селектор, когда я знал число параметров, для которых селектор ожидал, вот так:

SEL selector = NSSelectorFromString(@"FooWithOneArg");
[view performSelectorInBackground:selector withObject:someArg];

Я знаю, что могу получить количество аргументов, которые получает селектор, используя

int numberOfArguments = method_getNumberOfArguments(selector);

Но есть ли способ сделать общий вызов, подобный этому:

[someObject performSelector:selector withObject:arrayOfObjects]

, который в значительной степени эквивалентен Java

someMethod.invoke(someObject, argumentsArray[]);

Я хочу избежать случая переключения в соответствии с количеством аргументов, которые получает селектор.

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

Ответы [ 4 ]

12 голосов
/ 26 апреля 2011

Эта маленькая функция должна сделать свое дело, она не идеальна, но она дает вам отправную точку:

void invokeSelector(id object, SEL selector, NSArray *arguments)
{
    Method method = class_getInstanceMethod([object class], selector);
    int argumentCount = method_getNumberOfArguments(method);

    if(argumentCount > [arguments count])
        return; // Not enough arguments in the array

    NSMethodSignature *signature = [object methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:object];
    [invocation setSelector:selector];

    for(int i=0; i<[arguments count]; i++)
    {
        id arg = [arguments objectAtIndex:i];
        [invocation setArgument:&arg atIndex:i+2]; // The first two arguments are the hidden arguments self and _cmd
    }

    [invocation invoke]; // Invoke the selector
}
3 голосов
/ 29 февраля 2012

С удивительной помощью, включая простой, но отличный ответ от пользователя102008, я собрал следующий пример.Обратите внимание, что я действительно пытался разрешить кому-то посылать мне селектор цели, который либо принял, либо не принял аргумент.Если он принимает аргумент, я предполагаю, что они хотят, чтобы «я» вызывающего объекта возвращалось как ссылка:

    NSMethodSignature * sig = [target methodSignatureForSelector:selector];
    if ([sig numberOfArguments] > 0) {
        [target performSelector:selector withObject:self];
    }

    else {
        [target performSelector:selector];
    }

Надеюсь, это поможет кому-то копаться.

2 голосов
/ 27 февраля 2014

Я изменил ответ @JustSid и добавил дополнительную проверку, поддержку нулевых аргументов, изменил его на метод категории Obj-C NSObject и добавил вспомогательные методы -performSelectorIfAvailable: для более легкого использования.Пожалуйста, наслаждайтесь!:)

#import <objc/runtime.h>

@implementation NSObject (performSelectorIfAvailable)

// Invokes a selector with an arbitrary number of arguments.
// Non responding selector or too few arguments will make this method do nothing.
// You can pass [NSNull null] objects for nil arguments.
- (void)invokeSelector:(SEL)selector arguments:(NSArray*)arguments {
    if (![self respondsToSelector:selector]) return; // selector not found

    // From -numberOfArguments doc,
    // "There are always at least 2 arguments, because an NSMethodSignature object includes the hidden arguments self and _cmd, which are the first two arguments passed to every method implementation."
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    int numSelArgs = [signature numberOfArguments] - 2;
    if (numSelArgs > [arguments count]) return; // not enough arguments in the array

    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:self];
    [invocation setSelector:selector];

    for(int i=0; i < numSelArgs; i++) {
        id arg = [arguments objectAtIndex:i];
        if (![arg isKindOfClass:[NSNull class]]) {
            [invocation setArgument:&arg atIndex:i + 2];
        }
    }
    [invocation invoke]; // Invoke the selector
}
0 голосов
/ 26 апреля 2011

Почему бы не определить каждый из ваших методов, чтобы принимать один аргумент: массив объектов? Предположительно, что вы хотите, с помощью метода

-(void) doSomethingWithFoo:(id) foo andBar: (id) bar;

, чтобы вызвать его с параметрами, установленными из массива. Ну, вместо этого есть:

-(void) doSomethingWithArrayOfFooAndBar: (NSArray*) fooAndBar;

тогда весь ваш механизм отправки становится:

[someObject performSelector:selector withObject:arrayOfObjects];
...