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?

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

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

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:


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

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

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: 

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

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").


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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.