iOS - ¿Cómo implementar un performSelector con múltiples argumentos y con afterDelay?

90

Soy un novato en iOS. Tengo un método de selector de la siguiente manera:

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

}

Estoy tratando de implementar algo como esto:

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

Pero eso me da un error al decir:

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

¿Alguna idea de lo que me estoy perdiendo?

Suchi
fuente

Respuestas:

142

Personalmente, creo que una solución más cercana a sus necesidades es el uso de NSInvocation.

Algo como lo siguiente hará el trabajo:

indexPath y dataSource son dos variables de instancia definidas en el mismo 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];
}
valvolina
fuente
2
Convenido. Debería ser la respuesta correcta. Solución muy útil. Especialmente en mi caso, donde no está permitido cambiar la firma del método que contiene múltiples argumentos.
AbhijeetMishra
Esta parece una gran solución. ¿Hay alguna forma de obtener un valor de retorno del método que se invoca con esta técnica?
David Pettigrew
15
¿Cómo especificas el retraso con esta técnica?
death_au
4
@death_au, solo en lugar de invokellamar: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; Debo estar de acuerdo en que esta es una gran solución. ¡Feliz codificación para todos!
Maxim Chetrusca
2
Un poco tarde para la conversación, pero tengo una pregunta. ¿Qué es dropDownDelegate?
Minestrone-Soup
98

Porque no existe el [NSObject performSelector:withObject:withObject:afterDelay:]método.

Debe encapsular los datos que desea enviar en un solo objeto Objective C (por ejemplo, un NSArray, un NSDictionary, algún tipo Objective C personalizado) y luego pasarlos a través del [NSObject performSelector:withObject:afterDelay:]método que es bien conocido y querido.

Por ejemplo:

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

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Michael Dautermann
fuente
No obtengo un error si elimino el parámetro afterDelay. ¿Eso significa que afterDelay no puede usarse con más de un parámetro?
Suchi
1
no obtienes un error, pero apuesto a que obtendrás una excepción de "selector no encontrado" en tiempo de ejecución (y lo que estás tratando de realizar no se llamará) ... pruébalo y verás. :-)
Michael Dautermann
¿Cómo paso el tipo Bool aquí?
virata
Conviértalo en un objeto de estilo Objective C (por ejemplo, " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") y páselo como el único parámetro, @virata.
Michael Dautermann
2
esa es una pregunta separada @Raj ... por favor publíquela por separado.
Michael Dautermann
34

Puede empaquetar sus parámetros en un objeto y usar un método auxiliar para llamar a su método original como Michael y otros ahora han sugerido.

Otra opción es dispatch_after, que tomará un bloque y lo pondrá en cola en un momento determinado.

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

});

O, como ya ha descubierto, si no necesita la demora, puede usar - performSelector:withObject:withObject:

Firoze Lafeer
fuente
Lo que también es bueno de este enfoque es que puede usar __weakpara darle a su temporizador simulado solo un enlace débil a sí mismo, para que no termine extendiendo artificialmente el ciclo de vida de su objeto y, por ejemplo, si su performSelector: afterDelay: efectúa algo un poco como tail recursividad (aunque sin la recursividad) entonces resuelve el ciclo de retención.
Tommy
1
Sí, esta debería ser la respuesta aceptada. Es más apropiado y sencillo.
Roohul
7

La opción más simple es modificar su método para que tome un solo parámetro que contenga ambos argumentos, como un NSArrayo NSDictionary(o agregar un segundo método que tome un solo parámetro, lo descomprima y llame al primer método, y luego llame al segundo método en un retrasar).

Por ejemplo, podría tener algo como:

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

Y luego, para llamarlo, puedes hacer:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
aroth
fuente
¿Qué pasa si el método no se puede modificar, digamos que vive en UIKit o algo así? No solo eso, cambiar el método para usar también NSDictionarypierde la seguridad del tipo. No es ideal.
fatuhoku
@fatuhoku - Eso está cubierto por el paréntesis; "agregue un segundo método que tome un solo parámetro, lo descomprima y llame al primer método". Eso funciona independientemente de dónde viva el primer método. En cuanto a la seguridad de tipos, se perdió en el momento en que se tomó la decisión de usar performSelector:(o NSInvocation). Si eso le preocupa, la mejor opción probablemente sería pasar por GCD.
2015
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


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

}

y llámalo con:

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

Puede encontrar todos los tipos de métodos performSelector: proporcionados aquí:

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

Hay un montón de variaciones, pero no hay una versión que acepte varios objetos y un retraso. En su lugar, deberá resumir sus argumentos en un NSArray o 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
fuente
2

No me gusta la forma NSInvocation, demasiado compleja. Mantengamos las cosas simples y limpias:

// 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
fuente
¡Agradable! Reemplace el 'vc' con 'target'
Anton
1

Acabo de hacer swizzling y necesitaba llamar al método original. Lo que hice fue hacer un protocolo y ponerle mi objetivo. Otra forma es definir el método en una categoría, pero necesitaría la supresión de una advertencia (#pragma clang diagnostic ignorado "-Wincomplete-deployment").

darkfader
fuente
0

Una forma sencilla y reutilizable es ampliar 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
fuente
0

Simplemente crearía un objeto personalizado con todos mis parámetros como propiedades y luego usaría ese único objeto como parámetro

MobileMon
fuente