iOS - Come implementare un performSelector con più argomenti e con afterDelay?


90

Sono un principiante di iOS. Ho un metodo di selezione come segue:

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

}

Sto cercando di implementare qualcosa di simile -

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

Ma questo mi dà un errore dicendo:

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

Qualche idea su cosa mi manchi?

Risposte:


142

Personalmente, penso che una soluzione più vicina alle tue esigenze sia l'uso di NSInvocation.

Qualcosa come il seguente farà il lavoro:

indexPath e dataSource sono due variabili di istanza definite nello stesso metodo.

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
Concordato. Dovrebbe essere la risposta corretta. Soluzione molto utile. Specialmente nel mio caso in cui non è consentito modificare la firma del metodo contenente più argomenti.
AbhijeetMishra

Sembra un'ottima soluzione. C'è un modo per ottenere un valore di ritorno dal metodo richiamato con questa tecnica?
David Pettigrew

15
Come si specifica il ritardo con questa tecnica?
death_au

4
@death_au, invece di invoke, chiama: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; devo essere d'accordo che questa è un'ottima soluzione. Buona programmazione a tutti!
Maxim Chetrusca

2
È un po 'tardi per la conversazione, ma ho una domanda. Cos'è dropDownDelegate?
Minestrone-Soup

98

Perché non esiste un [NSObject performSelector:withObject:withObject:afterDelay:]metodo.

È necessario incapsulare i dati che si desidera inviare in un singolo oggetto Objective C (ad esempio un NSArray, un NSDictionary, un tipo Objective C personalizzato) e quindi passarli attraverso il [NSObject performSelector:withObject:afterDelay:]metodo che è ben noto e amato.

Per esempio:

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

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];

Non ricevo un errore se rimuovo il parametro afterDelay. Ciò significa che afterDelay non può essere utilizzato con più di un parametro?
Suchi

1
non ottieni un errore, ma scommetto che riceverai un'eccezione "selettore non trovato" in fase di esecuzione (e la cosa che stai cercando di eseguire non verrà chiamata) ... provalo e guarda. :-)
Michael Dautermann

Come faccio a passare il tipo Bool qui?
virata

Rendilo un oggetto in stile Objective C (ad esempio " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") e passalo come un parametro, @virata.
Michael Dautermann

2
questa è una domanda separata @Raj ... per favore pubblicala separatamente.
Michael Dautermann

34

Puoi impacchettare i tuoi parametri in un oggetto e usare un metodo di supporto per chiamare il tuo metodo originale come hanno suggerito Michael e altri ora.

Un'altra opzione è dispatch_after, che richiederà un blocco e lo accoderà in un determinato 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];

});

Oppure, come hai già scoperto, se non hai bisogno del ritardo puoi semplicemente usarlo - performSelector:withObject:withObject:


La cosa buona di questo approccio è che puoi usare __weakper dare al tuo finto timer solo un anello debole a te stesso - in modo da non finire per estendere artificialmente il ciclo di vita del tuo oggetto e, ad esempio, se il tuo performSelector: afterDelay: effettua qualcosa un po 'come coda ricorsione (anche se senza la ricorsione) poi risolve il ciclo di conservazione.
Tommy

1
Sì, questa dovrebbe essere la risposta accettata. È più appropriato e diretto.
Roohul

7

L'opzione più semplice è modificare il metodo per prendere un singolo parametro contenente entrambi gli argomenti, come un NSArrayor NSDictionary(o aggiungere un secondo metodo che accetta un singolo parametro, decomprimerlo e chiamare il primo metodo, quindi chiamare il secondo metodo su un ritardo).

Ad esempio, potresti avere qualcosa come:

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

E poi per chiamarlo, puoi fare:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];

E se il metodo non può essere modificato, diciamo che risiede in UIKit o qualcosa del genere? Non solo, cambiando il metodo da utilizzare si NSDictionaryperde anche l'indipendenza dai tipi. Non è l'ideale.
fatuhoku

@fatuhoku - Questo è coperto dalle parentesi; "aggiungi un secondo metodo che accetta un singolo parametro, lo decomprime e chiama il primo metodo". Funziona indipendentemente da dove risiede il primo metodo. Per quanto riguarda l'indipendenza dai tipi, ciò è andato perso nel momento in cui si è deciso di utilizzare performSelector:(o NSInvocation). Se questo è un problema, l'opzione migliore sarebbe probabilmente passare attraverso GCD.
aroth

6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


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

}

e chiamalo con:

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

5

Puoi trovare tutti i tipi di metodi performSelector: forniti qui:

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

Ci sono un sacco di variazioni ma non esiste una versione che accetta più oggetti e un ritardo. Dovrai invece racchiudere i tuoi argomenti in 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: 

2

Non mi piace il modo NSInvocation, troppo complesso. Manteniamolo semplice e pulito:

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

Bello! Sostituisci "vc" con "target"
Anton

1

Ho appena fatto un po 'di swizzling e avevo bisogno di chiamare il metodo originale. Quello che ho fatto è stato creare un protocollo e lanciarvi il mio oggetto. Un altro modo è definire il metodo in una categoria, ma richiederebbe la soppressione di un avviso (#pragma clang diagnostic ignorato "-Wincomplete-implementation").


0

Un modo semplice e riutilizzabile è estendere NSObjecte implementare

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

qualcosa di simile a:

- (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

Vorrei semplicemente creare un oggetto personalizzato tenendo tutti i miei parametri come proprietà, quindi utilizzare quel singolo oggetto come parametro

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.