Вызов селектора с неизвестным количеством аргументов с использованием отражения / самоанализа

#objective-c #ios #reflection #introspection

#objective-c #iOS #отражение #самоанализ

Вопрос:

Недавно я написал приложение на 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[]);
  

?

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

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

Ответ №1:

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

 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:amp;arg atIndex:i 2]; // The first two arguments are the hidden arguments self and _cmd
    }

    [invocation invoke]; // Invoke the selector
}
  

Комментарии:

1. class_getClassMethod([object class], selector); Я думаю, что это неправильно. Вы должны вызывать class_getInstanceMethod();

2. Спасибо, что предоставили мне эту помощь. Я обнаружил еще несколько проблем, и, если вы не возражаете, я исправлю их позже (или расскажу вам, что это такое) для дальнейшего использования. Еще раз спасибо!

3. Это не сработает при использовании с объектом класса, потому что вызов class объекта класса вызывает class , который просто возвращает сам объект класса, а не его класс. И в любом случае первые две строки не нужны, потому что вы можете просто получить argumentCount из [signature numberOfArguments]

Ответ №2:

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

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

    else {
        [target performSelector:selector];
    }
  

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

Комментарии:

1. Просто чтобы прояснить это, numberOfArguments всегда возвращает как минимум 2 (self, _cmd), поэтому выше следует прочитать [sig numberOfArguments] > 2 — developer.apple.com/library/mac/#documentation/Cocoa/Reference /…

Ответ №3:

Я изменил ответ @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:amp;arg atIndex:i   2];
        }
    }
    [invocation invoke]; // Invoke the selector
}
  

Ответ №4:

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

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

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

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

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

 [someObject performSelector:selector withObject:arrayOfObjects];
  

Комментарии:

1. Привет, ДжеремИ, я думал об этом, но другие методы не мои, это методы кода, который я тестирую. Мое приложение представляет собой своего рода RPC, который должен вызывать метод для некоторого объекта (управляемого с удаленного ПК). Я хочу максимально избежать переноса, чтобы упростить адаптацию нового API или изменений API.

2. @MByD: В таком случае, я думаю, вы застряли с методом JustSid . Это должно работать нормально.

3. Я бы не назвал это «застрявшим», это в значительной степени то, что я искал. Но я хочу посмотреть, работает ли это, прежде чем принимать ответ 🙂

4. @MByD: Я сказал «застрял» только потому, что необходимость прямого вызова среды выполнения Objective-C часто указывает на то, что с дизайном что-то не так. Я всегда чувствую, что должен быть способ сделать это на Objective-C. Однако в данном случае этого не происходит. Кстати, в решении есть ошибка.

5. Спасибо за объяснение. Objective-C — это новый мир для меня, и я знаю, что многое упускаю, к сожалению, у меня близкая мертвая точка, поэтому сначала я должен заставить все работать.