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?

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

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

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:


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

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

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: 

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

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


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

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.