iOS - как реализовать performSelector с несколькими аргументами и с afterDelay?

90

Я новичок в iOS. У меня есть метод выбора:

- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

Я пытаюсь реализовать что-то вроде этого -

[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second" afterDelay:15.0];

Но это дает мне ошибку:

Instance method -performSelector:withObject:withObject:afterDelay: not found

Любые идеи относительно того, что мне не хватает?

Сучи
источник

Ответы:

143

Лично я считаю, что более близким решением ваших задач является использование NSInvocation.

Что-то вроде следующего сделает работу:

indexPath и dataSource - две переменные экземпляра, определенные в одном методе.

SEL aSelector = NSSelectorFromString(@"dropDownSelectedRow:withDataSource:");

if([dropDownDelegate respondsToSelector:aSelector]) {
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
    [inv setSelector:aSelector];
    [inv setTarget:dropDownDelegate];

    [inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
    [inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation

    [inv invoke];
}
вальволин
источник
2
Согласовано. Это должен быть правильный ответ. Очень полезное решение. Особенно в моем случае, когда нельзя изменять подпись метода, содержащего несколько аргументов.
AbhijeetMishra
Похоже, это отличное решение. Есть ли способ получить возвращаемое значение из метода, вызываемого с помощью этой техники?
Дэвид Петтигрю
15
Как указать задержку в этой технике?
death_au
4
@death_au, просто вместо invokeзвонка: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; я согласен, это отличное решение. Всем удачного кодирования!
Максим Четруска
2
Вроде поздно к разговору, но есть вопрос. Что такое dropDownDelegate?
Minestrone-Soup
97

Потому что [NSObject performSelector:withObject:withObject:afterDelay:]метода не существует.

Вам необходимо инкапсулировать данные, которые вы хотите отправить, в какой-то один объект Objective C (например, NSArray, NSDictionary, некоторый настраиваемый тип Objective C), а затем передать их через [NSObject performSelector:withObject:afterDelay:]метод, который хорошо известен и любим.

Например:

NSArray * arrayOfThingsIWantToPassAlong = 
    [NSArray arrayWithObjects: @"first", @"second", nil];

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Михаэль Даутерманн
источник
Я не получаю сообщение об ошибке, если удаляю параметр afterDelay. Означает ли это, что afterDelay нельзя использовать более чем с одним параметром?
Сучи
1
вы не получите ошибки, но готов поспорить, что вы получите исключение «селектор не найден» во время выполнения (и то, что вы пытаетесь выполнить, не будет вызвано) ... попробуйте и убедитесь. :-)
Михаэль Даутерманн
Как передать здесь тип Bool?
virata
Сделайте его объектом в стиле Objective C (например, " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") и передайте его как один параметр, @virata.
Михаэль Даутерманн
2
это отдельный вопрос @Raj ... просьба выложить отдельно.
Михаэль Даутерманн,
34

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

Другой вариант - dispatch_after, который берет блок и ставит его в очередь в определенное время.

double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

    [self fooFirstInput:first secondInput:second];

});

Или, как вы уже обнаружили, если вам не нужна задержка, вы можете просто использовать - performSelector:withObject:withObject:

Фироз Лафеер
источник
Что также хорошо в этом подходе, так это то, что вы можете использовать __weakдля предоставления своему воображаемому таймеру только слабую связь с собой - так что вы не в конечном итоге искусственно продляете жизненный цикл вашего объекта и, например, если ваш performSelector: afterDelay: влияет на что-то вроде хвоста рекурсия (хотя и без рекурсии), то он разрешает цикл сохранения.
Томми
1
Да, это должен быть принятый ответ. Это более уместно и прямо.
Roohul
7

Самый простой вариант - изменить ваш метод так, чтобы он принимал один параметр, содержащий оба аргумента, например, NSArrayили NSDictionary(или добавить второй метод, который принимает один параметр, распаковывает его и вызывает первый метод, а затем вызывает второй метод для задержка).

Например, у вас может быть что-то вроде:

- (void) fooOneInput:(NSDictionary*) params {
    NSString* param1 = [params objectForKey:@"firstParam"];
    NSString* param2 = [params objectForKey:@"secondParam"];
    [self fooFirstInput:param1 secondInput:param2];
}

А затем, чтобы вызвать это, вы можете:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
арот
источник
Что, если метод нельзя изменить, скажем, он живет в UIKit или что-то в этом роде? Мало того, изменение метода использования также NSDictionaryтеряет безопасность типов. Не идеально.
fatuhoku
@fatuhoku - Это заключено в скобки; «добавить второй метод, который принимает единственный параметр, распаковывает его и вызывает первый метод». Это работает независимо от того, где живет первый метод. Что касается безопасности типов, то она была потеряна в тот момент, когда было принято решение использовать performSelector:(или NSInvocation). Если это вызывает беспокойство, лучшим вариантом, вероятно, будет пройти GCD.
aroth
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

и назовите его:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];
Майк Уоллес
источник
5

Вы можете найти все типы предоставленных методов performSelector: здесь:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html

Есть множество вариантов, но нет версии, которая принимает несколько объектов, а также задержку. Вместо этого вам нужно будет обернуть свои аргументы в NSArray или NSDictionary.

- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
– performSelector:withObject:afterDelay:
– performSelector:withObject:afterDelay:inModes:
– performSelectorOnMainThread:withObject:waitUntilDone:
– performSelectorOnMainThread:withObject:waitUntilDone:modes:
– performSelector:onThread:withObject:waitUntilDone:
– performSelector:onThread:withObject:waitUntilDone:modes:
– performSelectorInBackground:withObject: 
СтайлзКризис
источник
2

Мне не нравится способ NSInvocation, слишком сложный. Давайте оставим это простым и понятным:

// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;

// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];

// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);
BB9z
источник
Ницца! Замените 'vc' на 'target'
Антон
1

Я просто немного поиграл, и мне нужно было вызвать оригинальный метод. Я сделал протокол и передал ему свой объект. Другой способ - определить метод в категории, но для этого потребуется подавление предупреждения (#pragma clang Diagnosis игнорируется "-Wincomplete-implementation").

темный фейдер
источник
0

Простой и многоразовый способ - расширить NSObjectи реализовать

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;

что-то типа:

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
    NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
    [invocation setSelector: aSelector];

    int index = 2; //0 and 1 reserved
    for (NSObject *argument in arguments) {
        [invocation setArgument: &argument atIndex: index];
        index ++;
    }
    [invocation invokeWithTarget: self];
}
Каппе
источник
0

Я бы просто создал настраиваемый объект, содержащий все мои параметры как свойства, а затем использовал бы этот единственный объект в качестве параметра

MobileMon
источник