iOS - Como implementar um performSelector com vários argumentos e com afterDelay?

90

Eu sou um novato no iOS. Eu tenho um método seletor da seguinte forma -

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

}

Estou tentando implementar algo assim -

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

Mas isso me dá um erro ao dizer -

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

Alguma ideia do que estou perdendo?

Suchi
fonte

Respostas:

142

Pessoalmente, acho que uma solução mais próxima das suas necessidades é o uso do NSInvocation.

Algo como o seguinte fará o trabalho:

indexPath e dataSource são duas variáveis ​​de instância definidas no mesmo método.

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];
}
valvoline
fonte
2
Acordado. Deve ser a resposta correta. Solução muito útil. Especialmente no meu caso em que não é permitido alterar a assinatura do método que contém vários argumentos.
AbhijeetMishra
Esta parece ser uma ótima solução. Existe uma maneira de obter um valor de retorno do método que está sendo chamado com esta técnica?
David Pettigrew
15
Como você especifica o atraso com esta técnica?
death_au
4
@death_au, em vez de invoke, ligue: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; Devo concordar que essa é uma ótima solução. Feliz codificação a todos!
Maxim Chetrusca
2
Meio tarde para a conversa, mas tenho uma pergunta. O que é dropDownDelegate?
Minestrone-Soup
98

Porque não existe [NSObject performSelector:withObject:withObject:afterDelay:]método.

Você precisa encapsular os dados que deseja enviar em algum objeto Objective C único (por exemplo, um NSArray, um NSDictionary, algum tipo Objective C personalizado) e, em seguida, passá-los através do [NSObject performSelector:withObject:afterDelay:]método que é bem conhecido e amado.

Por exemplo:

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

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Michael Dautermann
fonte
Não recebo um erro se remover o parâmetro afterDelay. Isso significa que afterDelay não pode ser usado com mais de um parâmetro?
Suchi
1
você não obtém um erro, mas aposto que obteria uma exceção "seletor não encontrado" em tempo de execução (e o que você está tentando realizar não seria chamado) ... experimente e veja. :-)
Michael Dautermann
Como eu passo o tipo de Bool aqui?
virata
Torne-o um objeto de estilo Objective C (por exemplo, " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") e passe-o adiante como um parâmetro, @virata.
Michael Dautermann,
2
essa é uma pergunta separada @Raj ... por favor, poste separadamente.
Michael Dautermann
34

Você pode empacotar seus parâmetros em um objeto e usar um método auxiliar para chamar seu método original como Michael e outros agora sugeriram.

Outra opção é dispatch_after, que pegará um bloco e o enfileirará em um determinado momento.

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];

});

Ou, como você já descobriu, se não precisar do atraso, você pode apenas usar - performSelector:withObject:withObject:

Firoze Lafeer
fonte
O que também é bom sobre essa abordagem é que você pode usar __weakpara dar ao seu temporizador fingido apenas um elo fraco de volta para si mesmo - assim, você não acaba estendendo artificialmente o ciclo de vida do seu objeto e, por exemplo, se o seu desempenhoSeletor: afterDelay: afeta algo um pouco como cauda recursão (embora sem a recursão) então ele resolve o ciclo de retenção.
Tommy
1
Sim, esta deve ser a resposta aceita. É mais apropriado e direto.
Roohul
7

A opção mais simples é modificar seu método para pegar um único parâmetro contendo ambos os argumentos, como um NSArrayou NSDictionary(ou adicionar um segundo método que pega um único parâmetro, desempacota-o e chama o primeiro método e, em seguida, chama o segundo método em um demora).

Por exemplo, você poderia ter algo como:

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

E então, para chamá-lo, você pode fazer:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
aroth
fonte
E se o método não puder ser modificado, digamos que ele reside no UIKit ou algo assim? Além disso, mudar o método a ser usado também NSDictionaryperde a segurança de tipo. Não é ideal.
fatuhoku
@fatuhoku - Isso é coberto pelo parênteses; "adiciona um segundo método que leva um único parâmetro, descompacta e chama o primeiro método". Isso funciona independentemente de onde reside o primeiro método. Quanto à segurança de tipo, esta foi perdida no momento em que foi tomada a decisão de usar performSelector:(ou NSInvocation). Se isso for uma preocupação, a melhor opção provavelmente seria passar pelo GCD.
aroth
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


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

}

e chamá-lo com:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];
Mike Wallace
fonte
5

Você pode encontrar todos os tipos de métodos performSelector: fornecidos aqui:

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

Existem várias variações, mas não existe uma versão que aceite vários objetos e também um atraso. Em vez disso, você precisará agrupar seus argumentos em um NSArray ou 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: 
StilesCrisis
fonte
2

Não gosto da forma NSInvocation, muito complexa. Vamos mantê-lo simples e limpo:

// 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
fonte
Agradável! Substitua 'vc' por 'target'
Anton
1

Acabei de fazer alguns swizzling e precisei chamar o método original. O que fiz foi fazer um protocolo e lançar meu objeto a ele. Outra forma é definir o método em uma categoria, mas precisaria da supressão de um aviso (#pragma clang diagnostic Ignorado "-Wincomplete-implementação").

darkfader
fonte
0

Uma maneira simples e reutilizável é estender NSObjecte implementar

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

algo como:

- (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];
}
Kappe
fonte
0

Gostaria apenas de criar um objeto personalizado contendo todos os meus parâmetros como propriedades e, em seguida, usar esse único objeto como o parâmetro

MobileMon
fonte