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.