Jak używać performSelector: withObject: afterDelay: z typami prymitywnymi w kakao?


97

NSObjectMetoda performSelector:withObject:afterDelay:pozwala mi wywołać metodę na obiekt z argumentem obiektu po pewnym czasie. Nie można jej używać dla metod z argumentem niebędącym przedmiotem (np. Ints, floats, structs, non-object pointers itp.).

Jaki jest najprostszy sposób osiągnięcia tego samego za pomocą metody z argumentem niebędącym przedmiotem? Wiem, że na performSelector:withObject:co dzień rozwiązaniem jest użycie NSInvocation(co przy okazji jest naprawdę skomplikowane). Ale nie wiem, jak sobie poradzić z częścią „opóźnienia”.

Dzięki,


To trochę hackerskie, ale uważam, że przydatne jest pisanie szybkiego kodu. Coś jak: id object = [tablica performSelector: @selector (objectAtIndex :) withObject: (__bridge_transfer) (void *) 1]; . Jeśli argumentem jest 0, nie potrzebujesz nawet rzutowania mostu, ponieważ 0 jest „specjalne” i id jest z nim zgodne.
Ramy Al Zuhouri

Odpowiedzi:


72

Oto, co zwykłem nazywać czymś, czego nie mogłem zmienić za pomocą NSInvocation:

SEL theSelector = NSSelectorFromString(@"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
            invocationWithMethodSignature:
            [MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];

[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];

[anInvocation performSelector:@selector(invoke) withObject:nil afterDelay:1];

Dlaczego po prostu nie zrobić [theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]? Czy masz na myśli, że invokewiadomość jest w metodzie, którą wykonujesz po opóźnieniu?
Peter Hosey

Ach tak, zapomniałem powiedzieć ... `[anInvocation performSelector: @selector (invoke) afterDelay: 0.5];
Morty

24
uwaga na marginesie dla tych, którzy nie wiedzą, zaczynamy dodawać argumenty z indeksu 2, ponieważ indeksy 0 i 1 są zarezerwowane dla ukrytych argumentów „self” i „_cmd”.
Vishal Singh

35

Po prostu zawiń float, boolean, int lub podobny w NSNumber.

W przypadku struktur nie znam wygodnego rozwiązania, ale możesz stworzyć osobną klasę ObjC, która będzie właścicielem takiej struktury.


5
Utwórz metodę opakowującą, -delayedMethodWithArgument: (NSNumber *) arg. Niech wywoła oryginalną metodę po wyciągnięciu prymitywu z NSNumber.
James Williams

1
Zrozumiany. W takim razie nie jestem pewien eleganckiego rozwiązania, ale możesz dodać inną metodę, która jest przeznaczona jako cel twojego performSelector :, która rozpakowuje NSNumber i wykonuje ostateczny selektor na metodzie, którą masz obecnie na myśli. Innym pomysłem, który możesz zbadać, jest utworzenie kategorii na NSObject, która dodaje perforSelector: withInt: ... (i podobne).
szkodzi

32
NSValue (nadklasa NSNumber) zawinie wszystko, co mu podasz. Jeśli chcesz opakować instancję „struct foo” o nazwie „bar”, zrób to „[NSValue valueWithBytes: & bar objCType: @encode (struct foo)];”
Jim Dovey

2
Możesz użyć NSValue do zawijania struktury. Tak czy inaczej, NSNumber / NSValue jest właściwym rozwiązaniem.
Peter Hosey

6
Potwierdzone: MUSI przejść nildo BOOLparametru, aby otrzymać NO( FALSE)
Nicolas Miari

13

NIE UŻYWAJ TEJ ODPOWIEDZI. POZOSTAWIŁEM JEDYNIE W CELACH HISTORYCZNYCH. ZOBACZ PONIŻSZE UWAGI.

Istnieje prosta sztuczka, jeśli jest to parametr typu BOOL.

Pomiń zero na NIE, a siebie na TAK. zero jest rzutowane na wartość BOOL NO. self jest rzutowane na wartość BOOL równą YES.

To podejście nie działa, jeśli jest czymś innym niż parametr BOOL.

Zakładając, że ja jest UIView.

//nil will be cast to NO when the selector is performed
[self performSelector:@selector(setHidden:) withObject:nil afterDelay:5.0];

//self will be cast to YES when the selector is performed
[self performSelector:@selector(setHidden:) withObject:self afterDelay:10.0];

Uważam, że wartości BOOL powinny zawsze wynosić 0 lub 1. Prawdą jest, że większość kodu nie będzie się tym przejmować i po prostu wykona if ()na nim test, ale można sobie wyobrazić, że jakiś kod będzie zależał od tego, że będzie 0 lub 1, a zatem wygrywa nie traktuj dużej wartości adresu jako takiej samej jak 1 (TAK).
user102008

1
Dzięki za to, nie chciałem w tym celu tworzyć opakowania ani pisać brzydkiej składni GCD.
0xSina

10
NIE UŻYWAJ GO NIGDY . Takie podejście jest źródłem poważnych błędów, trudnych do znalezienia. Wyobraź sobie selftaki adres 0x0123400. Dzięki temu podejściu NIE otrzymasz TAK w 0,4% przypadków. Co gorsza, z takim prawdopodobieństwem to rozwiązanie może przejść wszystkie testy i ujawnić się później.
Oleg Trakhman

1
UPD: można by NIE zamiast TAK z 6% prawdopodobieństwem z powodu wyrównania pamięci.
Oleg Trakhman

4
@iVishal Zobacz ostre zakręty BOOL-a
Farray,

8

Być może NSValue , po prostu upewnij się, że wskaźniki są nadal aktualne po opóźnieniu (tj. Nie ma obiektów przydzielonych na stosie).


Czy jakaś stara metoda „rozpakuje” NSValue(jeśli to możliwe) zgodnie z oczekiwaniami type?
Alex Gray,

8

Wiem, że to stare pytanie, ale jeśli tworzysz iOS SDK 4+, możesz użyć bloków, aby to zrobić przy niewielkim wysiłku i uczynić go bardziej czytelnym:

double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self doSomethingWithPrimitive:primitiveValue];     
});

Tworzy to pętlę zatrzymania. Aby to działało poprawnie, musisz zrobić słabe odniesienie do siebie i użyć tego odniesienia do wykonania selektora. "__block typeof (self) słabySelf = siebie;"
Tim Wachter

1
Nie potrzebujesz tutaj słabego odniesienia, ponieważ self nie zachowuje bloku (nie ma pętli retain). Prawdopodobnie lepiej jest użyć silnego odniesienia, ponieważ w przeciwnym razie self może zostać zwolnione. Zobacz: stackoverflow.com/a/19018633/251687
Sebastien Martin

6

PerformSelector: WithObject zawsze pobiera obiekt, więc aby przekazać argumenty takie jak int / double / float itp ..... Możesz użyć czegoś takiego.

//NSNumber is an object..

    [self performSelector:@selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
    afterDelay:1.5];

    -(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];
    }

W ten sam sposób możesz użyć [NSNumber numberWithInt:] etc .... iw metodzie odbioru możesz przekonwertować liczbę na swój format jako [number int] lub [number double].


1
Jeśli jeden jest ograniczony do + performSelector: withObject: +, to jest to jedyna poprawna opublikowana odpowiedź, IMO. Ale odpowiedź @ MichaelGaylord za pomocą bloków jest czystsza, jeśli to dla ciebie opcja.
big_m

5

Bloki są do zrobienia. Możesz mieć złożone parametry, bezpieczeństwo typów i jest to o wiele prostsze i bezpieczniejsze niż większość starych odpowiedzi tutaj. Na przykład możesz po prostu napisać:

[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];

Bloki umożliwiają przechwytywanie dowolnych list parametrów, obiektów referencyjnych i zmiennych.

Implementacja wspierająca (podstawowa):

@interface MONBlock : NSObject

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;

@end

@implementation MONBlock

+ (void)imp_performBlock:(void(^)())pBlock
{
 pBlock();
}

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
  [self performSelector:@selector(imp_performBlock:)
             withObject:[pBlock copy]
             afterDelay:pDelay];
}

@end

Przykład:

int main(int argc, const char * argv[])
{
 @autoreleasepool {
  __block bool didPrint = false;
  int pi = 3; // close enough =p

  [MONBlock performBlock:^{NSLog(@"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];

  while (!didPrint) {
   [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
  }

  NSLog(@"(Bye, World!)");
 }
 return 0;
}

Zobacz także odpowiedź Michaela (+1) dla innego przykładu.


3

Zawsze zalecałbym używanie NSMutableArray jako obiektu do przekazania. Dzieje się tak, ponieważ możesz następnie przekazać kilka obiektów, takich jak naciśnięty przycisk i inne wartości. NSNumber, NSInteger i NSString to tylko kontenery o pewnej wartości. Upewnij się, że po uzyskaniu obiektu z tablicy, do której się odwołujesz, do odpowiedniego typu kontenera. Musisz przekazać kontenery NS. Tam możesz sprawdzić wartość. Pamiętaj, że kontenery używają isEqual podczas porównywania wartości.

#define DELAY_TIME 5

-(void)changePlayerGameOnes:(UIButton*)sender{
    NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
    NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
    [array addObject:nextPlayer];
    [self performSelector:@selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
    if(gdata != nil){ //if game choose next player
       [self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
    }
}

2

Ja też chciałem to zrobić, ale metodą, która otrzymuje parametr BOOL. Zawijanie wartości bool przy użyciu NSNumber NIE POWIODŁO SIĘ DO PRZESŁANIA WARTOŚCI. Nie mam pojęcia dlaczego.

Skończyło się na tym, że zrobiłem prosty hack. Umieściłem wymagany parametr w innej funkcji fikcyjnej i wywołałem tę funkcję za pomocą performSelector, gdzie withObject = nil;

[self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];

-(void)dummyCaller {

[self myFunction:YES];

}

Zobacz mój komentarz powyżej dotyczący odpowiedzi krzywd. Wygląda na to, że ponieważ -performSelector[...]metoda oczekuje obiektu (zamiast pierwotnego typu danych) i nie wie, że wywoływany selektor akceptuje wartość logiczną, być może adres (wartość wskaźnika) NSNumberinstancji jest ślepo „rzutowany” na BOOL(= niezerowe, tj TRUE.). Być może środowisko wykonawcze mogłoby być sprytniejsze niż to i rozpoznać zerową wartość logiczną opakowaną w NSNumber!
Nicolas Miari

Zgadzam się. Wydaje mi się, że lepszym rozwiązaniem jest całkowite uniknięcie BOOL i użycie liczby całkowitej 0 dla NIE i 1 dla TAK, a następnie owinięcie tego NSNumber lub NSValue.
GeneCode

Czy to coś zmienia? Jeśli tak, nagle jestem naprawdę rozczarowany kakao :)
Nicolas Miari,

2

Uważam, że najszybszym (ale nieco brudnym) sposobem na to jest bezpośrednie wywołanie objc_msgSend. Jednak wywołanie go bezpośrednio jest niebezpieczne, ponieważ musisz przeczytać dokumentację i upewnić się, że używasz prawidłowego wariantu dla typu wartości zwracanej, a ponieważ objc_msgSend jest zdefiniowany jako vararg dla wygody kompilatora, ale w rzeczywistości jest zaimplementowany jako szybki klej montażowy . Oto kod używany do wywołania metody delegata - [delegate integerDidChange:], która przyjmuje pojedynczy argument będący liczbą całkowitą.

#import <objc/message.h>


SEL theSelector = @selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
    typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
    IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
    MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}

Najpierw zapisuje selektor, ponieważ będziemy odwoływać się do niego wiele razy i łatwo byłoby wprowadzić literówkę. Następnie sprawdza, czy delegat faktycznie odpowiada selektorowi - może to być protokół opcjonalny. Następnie tworzy typ wskaźnika funkcji, który określa rzeczywistą sygnaturę selektora. Należy pamiętać, że wszystkie komunikaty Objective-C mają dwa ukryte pierwsze argumenty, wysyłany obiekt i wysyłany selektor. Następnie tworzymy wskaźnik funkcji odpowiedniego typu i ustawiamy go tak, aby wskazywał na podstawową funkcję objc_msgSend. Pamiętaj, że jeśli zwracana wartość jest zmiennoprzecinkową lub strukturą, musisz użyć innego wariantu objc_msgSend. Na koniec wyślij wiadomość za pomocą tej samej maszyny, której używa Objective-C pod arkuszami.


„Pamiętaj, że jeśli wysyłasz elementy zmiennoprzecinkowe lub struktury…” masz na myśli zwracanie
elementów zmiennoprzecinkowych

Te trzy wiersze zapewniają bezpieczeństwo typów i gwarantują, że liczba argumentów jest zgodna z oczekiwaniami selektora. objc_msgSend to funkcja vararg, która w rzeczywistości jest zaimplementowana jako klej montażowy, więc jeśli nie używasz typecast, możesz dać jej wszystko.
Jason Harris

1

Możesz po prostu użyć NSTimer do wywołania selektora:

[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(yourMethod:) userInfo:nil repeats:NO]

Brakuje ci argumentu. Argumentem do yourMethod:w twoim przykładzie jest sam licznik czasu, a nie przekazałeś niczego dla userInfo. Nawet gdybyś użył userInfo, nadal musiałbyś podnosić wartość, jak sugerował Harms i Remus Rusanu.
Peter Hosey,

-1 za nieużywanie performSelector i tworzenie niepotrzebnej instancji NSTimer
Applicasa iOS

1

Wywołanie performSelector z NSNumber lub innym NSValue nie zadziała. Zamiast używać wartości NSValue / NSNumber, efektywnie rzutuje wskaźnik na int, float lub cokolwiek innego i używa tego.

Ale rozwiązanie jest proste i oczywiste. Utwórz NSInvocation i wywołaj

[invocation performSelector:@selector(invoke) withObject:nil afterDelay:delay]


0

Pehaps ... ok, bardzo prawdopodobne, że czegoś mi brakuje, ale dlaczego nie utworzyć po prostu typu obiektu, powiedzmy NSNumber, jako kontenera dla zmiennej niebędącej typem obiektowym, takiej jak CGFloat?

CGFloat myFloat = 2.0; 
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];

[self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];

Pytanie dotyczy przekazywania typów pierwotnych, a nie obiektów NSNumber.
Rudolf Adamkovič

Pytanie zakłada, że ​​PO ma tylko zewnętrzny dostęp do metody i nie może zmienić podpisu.
Alex Gray,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.