iOS - Wie implementiere ich einen performSelector mit mehreren Argumenten und mit afterDelay?

90

Ich bin ein iOS-Neuling. Ich habe eine Auswahlmethode wie folgt:

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

}

Ich versuche so etwas umzusetzen -

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

Aber das gibt mir einen Fehler zu sagen -

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

Irgendwelche Ideen, was mir fehlt?

Suchi
quelle

Antworten:

142

Persönlich denke ich, dass eine engere Lösung für Ihre Bedürfnisse die Verwendung von NSInvocation ist.

So etwas wie das Folgende wird die Arbeit erledigen:

indexPath und dataSource sind zwei Instanzvariablen, die in derselben Methode definiert sind.

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];
}
Valvolin
quelle
2
Einverstanden. Es sollte die richtige Antwort sein. Sehr hilfreiche Lösung. Insbesondere in meinem Fall, in dem die Signatur der Methode mit mehreren Argumenten nicht geändert werden darf.
AbhijeetMishra
Das sieht nach einer großartigen Lösung aus. Gibt es eine Möglichkeit, einen Rückgabewert von der Methode zu erhalten, die mit dieser Technik aufgerufen wird?
David Pettigrew
15
Wie legen Sie die Verzögerung mit dieser Technik fest?
Death_au
4
@death_au, nur anstatt invokeanzurufen: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; Ich muss zustimmen, dass dies eine großartige Lösung ist. Viel Spaß beim Codieren!
Maxim Chetrusca
2
Etwas spät zum Gespräch, aber ich habe eine Frage. Was ist dropDownDelegate?
Minestrone-Suppe
98

Weil es keine [NSObject performSelector:withObject:withObject:afterDelay:]Methode gibt.

Sie müssen die Daten, die Sie senden möchten, in ein einzelnes Objective C-Objekt (z. B. ein NSArray, ein NSDictionary, einen benutzerdefinierten Objective C-Typ) einkapseln und dann die [NSObject performSelector:withObject:afterDelay:]bekannte und beliebte Methode durchlaufen .

Beispielsweise:

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

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Michael Dautermann
quelle
Ich erhalte keine Fehlermeldung, wenn ich den Parameter afterDelay entferne. Bedeutet das, dass afterDelay nicht mit mehr als einem Parameter verwendet werden darf?
Suchi
1
Sie erhalten keinen Fehler, aber ich wette, Sie würden zur Laufzeit eine Ausnahme "Selektor nicht gefunden" erhalten (und das, was Sie ausführen möchten, wird nicht aufgerufen) ... versuchen Sie es und sehen Sie. :-)
Michael Dautermann
Wie übergebe ich hier den Bool-Typ?
Virata
Machen Sie es zu einem Objekt im C-Stil (z. B. " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") und geben Sie es als den einen Parameter @virata weiter.
Michael Dautermann
2
das ist eine separate Frage @Raj ... bitte poste sie separat.
Michael Dautermann
34

Sie können Ihre Parameter in ein Objekt packen und eine Hilfsmethode verwenden, um Ihre ursprüngliche Methode aufzurufen, wie Michael und andere jetzt vorgeschlagen haben.

Eine weitere Option ist dispatch_after, bei der ein Block zu einem bestimmten Zeitpunkt in die Warteschlange gestellt wird.

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

});

Oder, wie Sie bereits festgestellt haben, wenn Sie die Verzögerung nicht benötigen, können Sie sie einfach verwenden - performSelector:withObject:withObject:

Firoze Lafeer
quelle
Das Gute an diesem Ansatz ist auch, dass Sie __weakIhrem vorgetäuschten Timer nur ein schwaches Glied zu sich selbst zurückgeben können, damit Sie den Lebenszyklus Ihres Objekts nicht künstlich verlängern und z. B. wenn Ihr performSelector: afterDelay: etwas wie Tail bewirkt Rekursion (wenn auch ohne Rekursion), dann wird der Aufbewahrungszyklus aufgelöst.
Tommy
1
Ja, dies sollte die akzeptierte Antwort sein. Es ist angemessener und unkomplizierter.
Roohul
7

Die einfachste Option besteht darin, Ihre Methode so zu ändern, dass ein einzelner Parameter verwendet wird, der beide Argumente enthält, z. B. ein NSArrayoder NSDictionary(oder eine zweite Methode hinzuzufügen, die einen einzelnen Parameter verwendet, ihn entpackt und die erste Methode aufruft und dann die zweite Methode für a aufruft verzögern).

Zum Beispiel könnten Sie so etwas haben wie:

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

Und um es dann zu nennen, können Sie Folgendes tun:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
Aroth
quelle
Was ist, wenn die Methode nicht geändert werden kann, beispielsweise in UIKit oder so? Darüber hinaus NSDictionaryverliert die Änderung der zu verwendenden Methode auch die Typensicherheit. Nicht ideal.
Fatuhoku
@fatuhoku - Das wird durch die Klammer abgedeckt; "Fügen Sie eine zweite Methode hinzu, die einen einzelnen Parameter verwendet, entpackt und die erste Methode aufruft." Das funktioniert unabhängig davon, wo die erste Methode lebt. Die Typensicherheit ging in dem Moment verloren, in dem die Entscheidung getroffen wurde, performSelector:(oder NSInvocation) zu verwenden. Wenn dies ein Problem darstellt, ist es wahrscheinlich die beste Option, die GCD zu durchlaufen.
aroth
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


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

}

und nenne es mit:

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

Hier finden Sie alle Arten der bereitgestellten performSelector: -Methoden:

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

Es gibt eine Reihe von Variationen, aber es gibt keine Version, die mehrere Objekte sowie eine Verzögerung akzeptiert. Sie müssen Ihre Argumente stattdessen in einem NSArray oder NSDictionary zusammenfassen.

- 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
quelle
2

Ich mag den zu komplexen NSInvocation-Weg nicht. Lassen Sie es uns einfach und sauber halten:

// 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
quelle
Nett! Ersetzen Sie das 'vc' durch 'target'
Anton
1

Ich habe nur ein bisschen geschwatzt und musste die ursprüngliche Methode aufrufen. Ich habe ein Protokoll erstellt und mein Objekt darauf geworfen. Eine andere Möglichkeit besteht darin, die Methode in einer Kategorie zu definieren, muss jedoch eine Warnung unterdrücken (#pragma clang Diagnose ignoriert "-Wincomplete-Implementierung").

Darkfader
quelle
0

Eine einfache und wiederverwendbare Möglichkeit ist das Erweitern NSObjectund Implementieren

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

etwas wie:

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

Ich würde einfach ein benutzerdefiniertes Objekt erstellen, das alle meine Parameter als Eigenschaften enthält, und dann dieses einzelne Objekt als Parameter verwenden

MobileMon
quelle