Używanie -performSelector: a nie tylko wywoływanie metody

116

Nadal jestem trochę nowy w Objective-C i zastanawiam się, jaka jest różnica między następującymi dwoma stwierdzeniami?

[object performSelector:@selector(doSomething)]; 

[object doSomething];
Hazardzista
źródło

Odpowiedzi:

191

Zasadniczo performSelector pozwala dynamicznie określić, który selektor wywoła selektor na danym obiekcie. Innymi słowy, selektor nie musi być określany przed uruchomieniem.

Tak więc, nawet jeśli są one równoważne:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 

Drugi formularz pozwala to zrobić:

SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

zanim wyślesz wiadomość.

ennuikiller
źródło
3
Warto zaznaczyć, że faktycznie przypisałbyś wynik funkcji findTheApprfficSelectorForTheCurrentSituation () do aSelector, a następnie wywołałby [anObject performSelector: aSelector]. @selector tworzy SEL.
Daniel Yankowsky,
4
Używanie performSelector:jest czymś, co prawdopodobnie robisz tylko wtedy, gdy zaimplementujesz akcję docelową w swojej klasie. Rodzeństwo performSelectorInBackground:withObject:i performSelectorOnMainThread:withObject:waitUntilDone:często są bardziej przydatne. Do tworzenia wątku w tle i do wywoływania wyników z wątku głównego z tego wątku w tle.
PeyloW
2
performSelectorjest również przydatny do pomijania ostrzeżeń kompilacji. Jeśli wiesz, że metoda istnieje (jak po użyciu respondsToSelector), Xcode przestanie mówić „może nie odpowiadać your_selector”. Po prostu nie używaj go, zamiast znaleźć prawdziwą przyczynę ostrzeżenia. ;)
Marc
Czytałem w innym wątku na StackOverflow, że użycie performSelector było odbiciem okropnego projektu i miało mnóstwo kciuków w górę. Chciałbym móc to znaleźć ponownie. Przeszukałem google, ograniczając wyniki do przepełnienia stosu i otrzymałem 18 000 wyników. Eww.
Logicsaurus Rex
„odbicie okropnego projektu” jest zbyt uproszczone. To było to, co mieliśmy, zanim były dostępne bloki, i nie wszystkie zastosowania są złe, wtedy czy teraz. Chociaż teraz, gdy bloki dostępne, jest to prawdopodobnie lepszy wybór dla nowego kodu, chyba że robisz coś bardzo prostego.
Ethan
16

W tym bardzo podstawowym przykładzie w pytaniu

[object doSomething];
[object performSelector:@selector(doSomething)]; 

nie ma różnicy w tym, co się wydarzy. Funkcja doSomething zostanie wykonana synchronicznie przez obiekt. Jedynie „doSomething” jest bardzo prostą metodą, która nic nie zwraca i nie wymaga żadnych parametrów.

czy było to coś bardziej skomplikowanego, na przykład:

(void)doSomethingWithMyAge:(NSUInteger)age;

sprawy by się skomplikowały, ponieważ [obiekt doSomethingWithMyAge: 42];

nie można już wywoływać z żadnym wariantem „performSelector”, ponieważ wszystkie warianty z parametrami akceptują tylko parametry obiektu.

Selektorem w tym miejscu byłoby „doSomethingWithMyAge:”, ale każda próba

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

po prostu się nie skompiluje. przekazanie NSNumber: @ (42) zamiast 42 też by nie pomogło, ponieważ metoda oczekuje podstawowego typu C, a nie obiektu.

Ponadto istnieją warianty performSelector do 2 parametrów, nie więcej. Chociaż metody wielokrotnie mają o wiele więcej parametrów.

Dowiedziałem się, że chociaż synchroniczne warianty performSelectora:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

zawsze zwracał obiekt, mogłem również zwrócić prosty BOOL lub NSUInteger i zadziałało.

Jednym z dwóch głównych zastosowań performSelector jest dynamiczne tworzenie nazwy metody, którą chcesz wykonać, jak wyjaśniono w poprzedniej odpowiedzi. Na przykład

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

Innym zastosowaniem jest asynchroniczne wysyłanie komunikatu do obiektu, który zostanie wykonany później na bieżącym runloopie. W tym celu istnieje kilka innych wariantów performSelector.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(tak, zebrałem je z kilku kategorii klas Foundation, takich jak NSThread, NSRunLoop i NSObject)

Każdy z wariantów ma swoje specjalne zachowanie, ale wszystkie mają coś wspólnego (przynajmniej wtedy, gdy parametr waitUntilDone ma wartość NIE). Wywołanie „performSelector” zwróciło by się natychmiast, a wiadomość do obiektu zostanie umieszczona w bieżącym runloop dopiero po pewnym czasie.

Z powodu opóźnionego wykonania - naturalnie żadna wartość zwracana nie jest dostępna z metody selektora, stąd wartość zwracana - (void) we wszystkich tych wariantach asynchronicznych.

Mam nadzieję, że jakoś to opisałem ...

Motti Shneor
źródło
12

@ennuikiller jest na miejscu. Zasadniczo selektory generowane dynamicznie są przydatne, gdy nie znasz (i zazwyczaj nie możesz) nazwy metody, którą będziesz wywoływać podczas kompilowania kodu.

Jedną kluczową różnicą jest to, że -performSelector:i przyjaciele (w tym warianty wielowątkowe i opóźnione ) są nieco ograniczone, ponieważ są zaprojektowane do użytku z metodami z parametrami 0-2. Na przykład wywołanie -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:z 6 parametrami i zwrócenie NSStringjest dość nieporęczne i nie jest obsługiwane przez dostarczone metody.

Quinn Taylor
źródło
5
Aby to zrobić, musiałbyś użyć NSInvocationobiektu.
Dave DeLong
6
Inna różnica: performSelector:wszyscy przyjaciele przyjmują argumenty obiektu, co oznacza, że ​​nie można ich używać do wywoływania (na przykład) setAlphaValue:, ponieważ ich argumentem jest liczba zmiennoprzecinkowa.
Chuck
4

Selektory są trochę podobne do wskaźników funkcji w innych językach. Używasz ich, gdy nie wiesz w czasie kompilacji, którą metodę chcesz wywołać w czasie wykonywania. Podobnie jak wskaźniki funkcji, zawierają one tylko część czasownika wywołania. Jeśli metoda ma parametry, musisz je również przekazać.

An NSInvocationsłuży podobnemu celowi, z tym wyjątkiem, że wiąże ze sobą więcej informacji. Zawiera nie tylko część czasownika, ale także obiekt docelowy i parametry. Jest to przydatne, gdy chcesz wywołać metodę na określonym obiekcie z określonymi parametrami, nie teraz, ale w przyszłości. Możesz zbudować odpowiedni NSInvocationi odpalić go później.

Daniel Yankowsky
źródło
5
Selektory w rzeczywistości wcale nie przypominają wskaźników do funkcji w tym sensie, że wskaźnik funkcji jest czymś, co można wywołać z argumentami, a selektor może być użyty do wywołania określonej metody na dowolnym obiekcie, który ją implementuje; selektor nie ma pełnego kontekstu wywołania, takiego jak wskaźnik funkcji.
bbum
1
Selektory to nie to samo, co wskaźniki funkcji, ale nadal uważam, że są podobne. Reprezentują czasowniki. Wskaźniki funkcji C również reprezentują czasowniki. Żadne nie jest przydatne bez dodatkowego kontekstu. Selektory wymagają obiektu i parametrów; wskaźniki funkcji wymagają parametrów (które mogą obejmować obiekt, na którym mają działać). Chodziło mi o podkreślenie, czym różnią się one od obiektów NSInvocation, które zawierają cały niezbędny kontekst. Być może moje porównanie było zagmatwane, w takim przypadku przepraszam.
Daniel Yankowsky
1
Selektory nie są wskaźnikami funkcji. Nawet nie blisko. W rzeczywistości są to proste łańcuchy C, które zawierają „nazwę” metody (w przeciwieństwie do „funkcji”). Nie są nawet sygnaturami metod, ponieważ nie zawierają typów parametrów. Obiekt może mieć więcej niż jedną metodę dla tego samego selektora (różne typy parametrów lub różne typy zwracane).
Motti Shneor
-7

Jest jeszcze jedna subtelna różnica między nimi.

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

Oto fragment dokumentacji Apple

"performSelector: withObject: afterDelay: Wykonuje określony selektor w bieżącym wątku podczas następnego cyklu pętli uruchamiania i po opcjonalnym okresie opóźnienia. Ponieważ czeka na następny cykl pętli uruchamiania, aby wykonać selektor, metody te zapewniają automatyczne mini opóźnienie od aktualnie wykonywany kod. Wiele selektorów w kolejce jest wykonywanych jeden po drugim w kolejności, w jakiej były w kolejce. "

avi
źródło
1
Twoja odpowiedź jest nieprawidłowa. Dokumentacja, o której cytujesz, dotyczy performSelector:withObject:afterDelay:, ale pytanie i Twój fragment używają performSelector:, co jest zupełnie inną metodą. Z dokumentacji: <quote> performSelector:Metoda jest równoważna wysłaniu aSelectorwiadomości bezpośrednio do odbiorcy. </quote>
jscs
3
dzięki Josh za wyjaśnienie. Masz rację; Myślałem, że performSelector/performSelector:withObject/performSelector:withObject:afterDelaywszyscy zachowywali się w ten sam sposób, co było błędem.
avi