iOS - Jak zaimplementować performSelector z wieloma argumentami i afterDelay?

90

Jestem nowicjuszem na iOS. Mam następującą metodę selektora -

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

}

Próbuję zaimplementować coś takiego -

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

Ale to daje mi błąd, mówiąc:

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

Jakieś pomysły, czego mi brakuje?

Suchi
źródło

Odpowiedzi:

143

Osobiście uważam, że bliższym rozwiązaniem Twoich potrzeb jest użycie NSInvocation.

Coś takiego zadziała:

indexPath i dataSource to dwie zmienne instancji zdefiniowane w tej samej metodzie.

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];
}
valvoline
źródło
2
Zgoda. To powinna być poprawna odpowiedź. Bardzo pomocne rozwiązanie. Szczególnie w moim przypadku, gdy nie można zmienić podpisu metody zawierającej wiele argumentów.
AbhijeetMishra
To wygląda na świetne rozwiązanie. Czy istnieje sposób uzyskania wartości zwracanej z metody wywoływanej za pomocą tej techniki?
David Pettigrew
15
Jak określasz opóźnienie w tej technice?
death_au
4
@death_au, zamiast invokedzwonić: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; Muszę się zgodzić, że to świetne rozwiązanie. Miłego kodowania wszystkim!
Maxim Chetrusca
2
Trochę późno do rozmowy, ale mam pytanie. Co to jest dropDownDelegate?
Minestrone-Soup
97

Ponieważ nie ma czegoś takiego jak [NSObject performSelector:withObject:withObject:afterDelay:]metoda.

Musisz umieścić dane, które chcesz wysłać, do pojedynczego obiektu Objective C (np. NSArray, NSDictionary, jakiś niestandardowy typ Objective C), a następnie przekazać je [NSObject performSelector:withObject:afterDelay:]metodą, która jest dobrze znana i lubiana.

Na przykład:

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

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Michael Dautermann
źródło
Nie pojawia się błąd, jeśli usunę parametr afterDelay. Czy to oznacza, że ​​afterDelay nie może być używany z więcej niż jednym parametrem?
Suchi,
1
nie pojawia się błąd, ale założę się, że w czasie wykonywania pojawi się wyjątek „nie znaleziono selektora” (a rzecz, którą próbujesz wykonać, nie zostanie wywołana) ... spróbuj i zobacz. :-)
Michael Dautermann
Jak przekazać tutaj typ Bool?
virata
Uczyń go obiektem w stylu Objective C (np. „ NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];”) I przekaż go jako jedyny parametr @virata.
Michael Dautermann
2
to osobne pytanie @Raj ... napisz je osobno.
Michael Dautermann
34

Możesz spakować swoje parametry w jeden obiekt i użyć metody pomocniczej, aby wywołać swoją oryginalną metodę, jak zasugerował Michael i inni.

Inną opcją jest dispatch_after, który pobierze blok i umieści go w kolejce w określonym czasie.

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

});

Lub, jak już odkryłeś, jeśli nie potrzebujesz opóźnienia, możesz po prostu użyć - performSelector:withObject:withObject:

Firoze Lafeer
źródło
To, co jest dobre w tym podejściu, polega na tym, że możesz użyć, __weakaby dać swojemu zegarowi udawanego tylko słabe łącze z powrotem do siebie - więc nie kończysz sztucznie przedłużając cykl życia obiektu i np. Jeśli twój performSelector: afterDelay: powoduje coś podobnego do tail rekurencja (aczkolwiek bez rekursji) to rozwiązuje cykl utrzymania.
Tommy
1
Tak, to powinna być akceptowana odpowiedź. Jest to bardziej odpowiednie i proste.
Roohul
7

Najprostszą opcją jest zmodyfikowanie metody tak, aby pobierała pojedynczy parametr zawierający oba argumenty, na przykład NSArraylub NSDictionary(lub dodać drugą metodę, która przyjmuje pojedynczy parametr, rozpakowuje go i wywołuje pierwszą metodę, a następnie wywołuje drugą metodę w opóźnienie).

Na przykład możesz mieć coś takiego:

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

A potem, aby to nazwać, możesz zrobić:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
aroth
źródło
A co jeśli metody nie można zmodyfikować, powiedzieć, że żyje w UIKit czy coś w tym stylu? Mało tego, zmiana metody użycia powoduje również NSDictionaryutratę bezpieczeństwa typów. Nieidealny.
fatuhoku
@fatuhoku - to jest objęte nawiasami; „dodaj drugą metodę, która pobiera pojedynczy parametr, rozpakowuje go i wywołuje pierwszą metodę”. Działa to niezależnie od tego, gdzie żyje pierwsza metoda. Jeśli chodzi o bezpieczeństwo typu, zostało to utracone w momencie podjęcia decyzji o użyciu performSelector:(lub NSInvocation). Jeśli jest to problem, najlepszą opcją byłoby prawdopodobnie przejście przez GCD.
aroth
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


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

}

i zadzwoń z:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];
Mike Wallace
źródło
5

Wszystkie typy dostępnych metod performSelector: można znaleźć tutaj:

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

Istnieje wiele odmian, ale nie ma wersji, która pobiera wiele obiektów, a także opóźnienie. Zamiast tego będziesz musiał zawrzeć swoje argumenty w NSArray lub 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
źródło
2

Nie podoba mi się sposób NSInvocation, zbyt złożony. Niech to będzie proste i przejrzyste:

// 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
źródło
Ładny! Zamień „vc” na „cel”
Anton
1

Po prostu zawirowałem i musiałem wywołać oryginalną metodę. Robiłem protokół i rzucałem na niego mój obiekt. Innym sposobem jest zdefiniowanie metody w kategorii, ale wymagałoby to pominięcia ostrzeżenia (zignorowano #pragma clang diagnostyka „-Wincomplete-Implementation”).

darkfader
źródło
0

Prostym sposobem wielokrotnego użytku jest rozszerzenie NSObjecti wdrożenie

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

coś jak:

- (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
źródło
0

Po prostu utworzyłbym obiekt niestandardowy zawierający wszystkie moje parametry jako właściwości, a następnie użyłbym tego pojedynczego obiektu jako parametru

MobileMon
źródło