Rozwiązanie
Kompilator ostrzega o tym z jakiegoś powodu. Bardzo rzadko to ostrzeżenie należy po prostu zignorować i łatwo się obejść. Oto jak:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Lub bardziej zwięźle (choć trudny do odczytania i bez osłony):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Wyjaśnienie
Chodzi o to, że pytasz kontroler o wskaźnik funkcji C dla metody odpowiadającej kontrolerowi. Wszystkie NSObject
reagują methodForSelector:
, ale możesz także użyć class_getMethodImplementation
w środowisku wykonawczym Objective-C (przydatne, jeśli masz tylko odwołanie do protokołu, takie jak id<SomeProto>
). Te wskaźniki funkcji są nazywane IMP
s i są prostymi typedef
wskaźnikami funkcji ed ( id (*IMP)(id, SEL, ...)
) 1 . Może to być zbliżone do rzeczywistej sygnatury metody, ale nie zawsze będzie dokładnie pasować.
Gdy już go IMP
masz, musisz rzucić go na wskaźnik funkcji, który zawiera wszystkie szczegóły potrzebne ARC (w tym dwa ukryte argumenty ukryte self
i _cmd
każde wywołanie metody Objective-C). Jest to obsługiwane w trzecim wierszu ( (void *)
po prawej stronie informuje kompilator, że wiesz, co robisz, i nie generuje ostrzeżenia, ponieważ typy wskaźników nie pasują).
Na koniec wywołujesz wskaźnik funkcji 2 .
Złożony przykład
Kiedy selektor przyjmuje argumenty lub zwraca wartość, musisz nieco zmienić rzeczy:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
Uzasadnienie ostrzeżenia
Powodem tego ostrzeżenia jest to, że w przypadku ARC środowisko wykonawcze musi wiedzieć, co zrobić z wynikiem wywoływanej metody. Rezultatem może być cokolwiek: void
, int
, char
, NSString *
, id
, itd. ARC normalnie pobiera te informacje z nagłówka typu obiektu ty pracujesz. 3)
Są naprawdę tylko 4 rzeczy, które ARC rozważa dla wartości zwracanej: 4
- Rodzaje ignorować niż przedmiot (
void
, int
itp)
- Zachowaj wartość obiektu, a następnie zwolnij ją, gdy nie będzie już używana (założenie standardowe)
- Zwolnij nowe wartości obiektów, gdy nie będą już używane (metody w
init
/ copy
rodzinie lub przypisane do ns_returns_retained
)
- Nie rób nic i zakładaj, że zwrócona wartość obiektu będzie poprawna w zasięgu lokalnym (do momentu wyczerpania wewnętrznej puli zwolnień, przypisanej przez
ns_returns_autoreleased
)
Wywołanie to methodForSelector:
zakłada, że wartość zwracana przez wywoływaną metodę jest obiektem, ale go nie zachowuje / nie zwalnia. Możesz więc spowodować wyciek, jeśli twój obiekt ma zostać zwolniony jak w punkcie 3 powyżej (to znaczy, że wywoływana metoda zwraca nowy obiekt).
W przypadku selektorów, które próbujesz nazwać tym zwrotem void
lub innymi obiektami niebędącymi obiektami, możesz włączyć funkcje kompilatora, aby zignorować ostrzeżenie, ale może to być niebezpieczne. Widziałem, jak Clang przechodzi kilka iteracji tego, w jaki sposób obsługuje zwracane wartości, które nie są przypisane do zmiennych lokalnych. Nie ma powodu, aby po włączeniu ARC nie można było zachować i zwolnić wartości obiektu, która została zwrócona, methodForSelector:
nawet jeśli nie chcesz jej używać. Z perspektywy kompilatora jest to jednak obiekt. Oznacza to, że jeśli metoda, którą wywołujesz, someMethod
zwraca obiekt niebędący obiektem (w tym void
), możesz skończyć z zachowaniem / zwolnieniem wartości wskaźnika śmieci i awarią.
Dodatkowe argumenty
Jednym z powodów jest to, że pojawi się to samo ostrzeżenie performSelector:withObject:
i możesz napotkać podobne problemy, nie deklarując, w jaki sposób ta metoda zużywa parametry. ARC pozwala na deklarowanie zużytych parametrów , a jeśli metoda zużywa parametr, prawdopodobnie ostatecznie wyślesz wiadomość do zombie i nastąpi awaria. Istnieją sposoby obejścia tego problemu przy użyciu mostkowania rzutowania, ale tak naprawdę lepiej byłoby po prostu użyć IMP
powyższej metodologii wskaźnika i funkcji. Ponieważ zużyte parametry rzadko stanowią problem, prawdopodobnie się nie pojawi.
Selektory statyczne
Co ciekawe, kompilator nie będzie narzekał na selektory zadeklarowane statycznie:
[_controller performSelector:@selector(someMethod)];
Powodem tego jest fakt, że kompilator faktycznie może rejestrować wszystkie informacje o selektorze i obiekcie podczas kompilacji. Nie musi niczego zakładać. (Sprawdziłem to rok temu, patrząc na źródło, ale nie mam teraz odniesienia.)
Tłumienie
Próbując wymyślić sytuację, w której konieczne byłoby zniesienie tego ostrzeżenia i dobry projekt kodu, wychodzę na pustkę. Ktoś proszę podzielić się, jeśli miał doświadczenie, w którym wyciszenie tego ostrzeżenia było konieczne (a powyższe nie radzi sobie właściwie).
Więcej
Można to również zbudować, NSMethodInvocation
aby sobie z tym poradzić, ale wymaga to znacznie więcej pisania i jest wolniejsze, więc nie ma powodu, aby to robić.
Historia
Kiedy performSelector:
rodzina metod została po raz pierwszy dodana do celu C, ARC nie istniało. Tworząc ARC, Apple zdecydował, że należy wygenerować ostrzeżenie dla tych metod jako sposób na poprowadzenie programistów do użycia innych środków do jawnego zdefiniowania sposobu obsługi pamięci podczas wysyłania dowolnych wiadomości za pośrednictwem nazwanego selektora. W Objective-C programiści mogą to zrobić, używając rzutów w stylu C na surowych wskaźnikach funkcji.
Wraz z wprowadzeniem Swift, Apple został udokumentowany na performSelector:
rodzinę metod jako „z natury niebezpieczne” i nie są one dostępne do Swift.
Z biegiem czasu zaobserwowaliśmy następujący postęp:
- Wczesne wersje Objective-C pozwalają
performSelector:
(ręczne zarządzanie pamięcią)
- Cel C z ARC ostrzega przed użyciem
performSelector:
- Swift nie ma dostępu do
performSelector:
tych metod i dokumentuje je jako „z natury niebezpieczne”
Idea wysyłania wiadomości na podstawie nazwanego selektora nie jest jednak funkcją „z natury niebezpieczną”. Pomysł ten był z powodzeniem stosowany od dawna w Objective-C, a także w wielu innych językach programowania.
1 Wszystkie metody Objective-C mają dwa ukryte argumenty, self
a _cmd
które są domyślnie dodawane podczas wywołania metody.
2 Wywołanie NULL
funkcji nie jest bezpieczne w C. Osłona używana do sprawdzania obecności kontrolera zapewnia, że mamy obiekt. Dlatego wiem, że otrzymujemy IMP
z methodForSelector:
(choć może to być _objc_msgForward
, wejście do systemu przekazywania wiadomości). Zasadniczo, gdy strażnik jest na miejscu, wiemy, że mamy funkcję do wywołania.
3 W rzeczywistości możliwe jest uzyskanie błędnych informacji, jeśli zadeklarujesz obiekty jako id
i nie importujesz wszystkich nagłówków. Może dojść do awarii kodu, które kompilator uważa za prawidłowe. Jest to bardzo rzadkie, ale może się zdarzyć. Zwykle pojawia się ostrzeżenie, że nie wie, z której z dwóch sygnatur metody wybrać.
4 Aby uzyskać więcej informacji, zobacz odniesienie do ARC dotyczące zachowanych wartości zwrotnych i niezatwierdzonych wartości zwrotnych .