Otrzymuję następujące ostrzeżenie przez kompilator ARC:
"performSelector may cause a leak because its selector is unknown".
Oto co robię:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
Dlaczego dostaję to ostrzeżenie? Rozumiem, że kompilator nie może sprawdzić, czy selektor istnieje, czy nie, ale dlaczego miałoby to spowodować wyciek? Jak mogę zmienić kod, aby nie wyświetlało się już to ostrzeżenie?
ios
objective-c
memory-leaks
automatic-ref-counting
Eduardo Scoz
źródło
źródło
Odpowiedzi:
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:
Lub bardziej zwięźle (choć trudny do odczytania i bez osłony):
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 jakid<SomeProto>
). Te wskaźniki funkcji są nazywaneIMP
s i są prostymitypedef
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 ukryteself
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:
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
void
,int
itp)init
/copy
rodzinie lub przypisane dons_returns_retained
)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 tymvoid
), 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:
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:
performSelector:
(ręczne zarządzanie pamięcią)performSelector:
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 otrzymujemyIMP
zmethodForSelector:
(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 .
źródło
performSelector:
metody nie są zaimplementowane w ten sposób. Mają ścisłą sygnaturę metody (zwracanieid
, przyjmowanie jednej lub dwóchid
s), więc nie trzeba obsługiwać typów pierwotnych.Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
przy korzystaniu z najnowszego Xcode. (5.1.1) Mimo to wiele się nauczyłem!void (*func)(id, SEL) = (void *)imp;
nie kompiluje się, zastąpiłem govoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp;
na<…> = (void (*))imp;
lub<…> = (void (*) (id, SEL))imp;
W kompilatorze LLVM 3.0 w Xcode 4.2 możesz wyłączyć ostrzeżenie w następujący sposób:
Jeśli błąd pojawia się w kilku miejscach i chcesz użyć systemu makr C do ukrycia pragm, możesz zdefiniować makro, aby ułatwić wyłączenie ostrzeżenia:
Możesz użyć makra w następujący sposób:
Jeśli potrzebujesz wyniku wykonanej wiadomości, możesz to zrobić:
źródło
pop
ipush
-pragmas są znacznie czystsze i bezpieczniejsze.if ([_target respondsToSelector:_selector]) {
podobną lub podobną logiką.Domyślam się, że: ponieważ selektor nie jest znany kompilatorowi, ARC nie może wymusić właściwego zarządzania pamięcią.
W rzeczywistości zdarza się, że zarządzanie pamięcią jest powiązane z nazwą metody według określonej konwencji. W szczególności myślę o konstruktorach wygody a metodach make ; poprzedni zwrócił konwencją automatycznie wydany obiekt; ten ostatni jest zachowanym przedmiotem. Konwencja opiera się na nazwach selektora, więc jeśli kompilator nie zna selektora, nie może egzekwować właściwej reguły zarządzania pamięcią.
Jeśli jest to poprawne, myślę, że możesz bezpiecznie używać swojego kodu, pod warunkiem, że upewnisz się, że wszystko jest w porządku pod względem zarządzania pamięcią (np. Że twoje metody nie zwracają przydzielonych obiektów).
źródło
__attribute
do każdej metody wyjaśniającej zarządzanie pamięcią. Uniemożliwia to również kompresorowi prawidłowe obsługiwanie tego wzorca (wzorzec, który był bardzo powszechny, ale w ostatnich latach został zastąpiony bardziej solidnymi wzorami).SEL
i przypisywać różnych selektorów w zależności od sytuacji?W projekcie Ustawienia budowania , w obszarze Inne flagi ostrzegawcze (
WARNING_CFLAGS
), dodaj-Wno-arc-performSelector-leaks
Teraz upewnij się, że selektor, do którego dzwonisz, nie powoduje zatrzymania lub skopiowania obiektu.
źródło
Aby obejść ten problem, dopóki kompilator nie będzie mógł zastąpić ostrzeżenia, można użyć środowiska wykonawczego
zamiast
Będziesz musiał
źródło
[_controller performSelector:NSSelectorFromString(@"someMethod")];
iobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
nie są równoważne! Spójrz na niedopasowania sygnatur metod i dużą słabość w słabym pisaniu w Celu-C wyjaśniają one dogłębnie problem.Aby zignorować błąd tylko w pliku z selektorem perform, dodaj #pragma w następujący sposób:
To zignoruje ostrzeżenie w tym wierszu, ale nadal pozwoli na to w pozostałej części projektu.
źródło
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
. Wiem, że jeśli wyłączę ostrzeżenie, lubię je włączyć w najbliższym możliwym momencie, więc nie mogę przypadkowo pozwolić, by przeszło kolejne nieoczekiwane ostrzeżenie. Jest mało prawdopodobne, że jest to problem, ale to tylko moja praktyka, gdy wyłączam ostrzeżenie.#pragma clang diagnostic warning push
przed wprowadzeniem jakichkolwiek zmian i#pragma clang diagnostic warning pop
przywrócić poprzedni stan. Przydatne, jeśli wyłączasz obciążenia i nie chcesz mieć wielu linii ponownego pragma w kodzie.Dziwne, ale prawdziwe: jeśli dopuszczalne (tzn. Wynik jest nieważny i nie masz nic przeciwko jednokrotnemu cyklowi wybiegu), dodaj opóźnienie, nawet jeśli jest to zero:
Usuwa to ostrzeżenie, prawdopodobnie dlatego, że zapewnia kompilatorowi, że żaden obiekt nie może zostać zwrócony i jakoś źle zarządzony.
źródło
Oto zaktualizowane makro na podstawie odpowiedzi podanej powyżej. Ten powinien pozwolić ci owinąć kod nawet z instrukcją return.
źródło
return
nie musi znajdować się w makrze;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
działa również i wygląda zdrowiej.Ten kod nie obejmuje flag kompilatora ani bezpośrednich wywołań środowiska wykonawczego:
NSInvocation
umożliwia ustawienie wielu argumentów, więc w przeciwieństwie doperformSelector
tego będzie działać na dowolnej metodzie.źródło
Cóż, wiele odpowiedzi tutaj, ale ponieważ jest to trochę inne, łącząc kilka odpowiedzi, pomyślałem, że je wstawię. Korzystam z kategorii NSObject, która sprawdza, czy selektor zwraca wartość void, a także pomija kompilator ostrzeżenie.
źródło
Dla potomności postanowiłem rzucić kapelusz na ring :)
Ostatnio byłem widząc coraz bardziej restrukturyzacji od
target
/selector
paradygmatu, za rzeczy, takich jak protokoły, bloków itp Jednak istnieje jeden zamiennik dlaperformSelector
że użyłem kilka razy:Wydają się być czystym, bezpiecznym dla ARC i prawie identycznym zamiennikiem,
performSelector
bez konieczności zbytniego angażowania sięobjc_msgSend()
.Chociaż nie mam pojęcia, czy w iOS jest dostępny analog.
źródło
[[UIApplication sharedApplication] sendAction: to: from: forEvent:]
. Przyglądałem się temu raz, ale trochę dziwnie jest używać klasy związanej z interfejsem użytkownika w środku domeny lub usługi tylko po to, aby wykonać dynamiczne połączenie. Dziękuję za to!id
from-performSelector:...
to:
ma zero, co nie jest. Po prostu idzie prosto do obiektu docelowego bez wcześniejszego sprawdzania. Więc nie ma „więcej kosztów ogólnych”. To nie jest świetne rozwiązanie, ale podany powód nie jest powodem. :)Odpowiedź Matta Gallowaya w tym wątku wyjaśnia, dlaczego:
Wydaje się, że można bezpiecznie wyłączyć ostrzeżenie, jeśli ignorujesz zwracaną wartość. Nie jestem pewien, jaka jest najlepsza praktyka, jeśli naprawdę potrzebujesz uzyskać zachowany obiekt z performSelector - inny niż „nie rób tego”.
źródło
@ c-road zapewnia właściwą więź z opisem problemu tutaj . Poniżej możesz zobaczyć mój przykład, kiedy performSelector powoduje wyciek pamięci.
Jedyną metodą, która powoduje wyciek pamięci w moim przykładzie, jest CopyDummyWithLeak. Powodem jest to, że ARC nie wie, że copySelector zwraca zachowany obiekt.
Jeśli uruchomisz narzędzie Memory Leak Tool, możesz zobaczyć następujący obrazek: ... i w żadnym innym przypadku nie ma wycieków pamięci:
źródło
Aby uczynić makro Scotta Thompsona bardziej ogólnym:
Następnie użyj go w następujący sposób:
źródło
Nie usuwaj ostrzeżeń!
Istnieje nie mniej niż 12 alternatywnych rozwiązań majstrowania przy kompilatorze.
Podczas gdy jesteś sprytny w momencie pierwszej implementacji, niewielu inżynierów na Ziemi może podążać twoimi śladami, a ten kod w końcu się zepsuje.
Bezpieczne trasy:
Wszystkie te rozwiązania będą działać z pewną różnicą w stosunku do pierwotnego celu. Załóż, że
param
może tak być,nil
jeśli chcesz:Bezpieczna trasa, to samo zachowanie koncepcyjne:
Bezpieczna trasa, nieco inne zachowanie:
(Zobacz tę odpowiedź)
Użyj dowolnego wątku zamiast
[NSThread mainThread]
.Niebezpieczne trasy
Wymaga pewnego rodzaju wyciszenia kompilatora, który musi się zepsuć. Zauważ, że w chwili obecnej, to zrobił przerwę w Swift .
źródło
performSelectorOnMainThread
to nie jest dobrym sposobem, aby wyciszyć ostrzeżenia i nie ma skutków ubocznych. (nie rozwiązuje wycieku pamięci) Dodatkowy#clang diagnostic ignored
wyraźnie tłumi ostrzeżenie w bardzo wyraźny sposób.- (void)
metodzie jest prawdziwym problemem.Ponieważ używasz ARC, musisz używać iOS 4.0 lub nowszego. Oznacza to, że możesz użyć bloków. Jeśli zamiast zapamiętać selektor do wykonania, zamiast tego wziąłeś blok, ARC byłby w stanie lepiej śledzić, co się właściwie dzieje i nie musiałbyś ryzykować przypadkowego wprowadzenia wycieku pamięci.
źródło
self
przez ivar (np.ivar
Zamiastself->ivar
).Zamiast stosować podejście blokowe, co dało mi pewne problemy:
Użyję NSInvocation, tak jak to:
źródło
Jeśli nie musisz przekazywać żadnych argumentów, skorzystaj z łatwego obejścia
valueForKeyPath
. Jest to nawet możliwe naClass
obiekcie.źródło
Możesz również użyć protokołu tutaj. Utwórz więc taki protokół:
W klasie, która musi zadzwonić do selektora, masz @ właściwość.
Jeśli chcesz wywołać
@selector(doSomethingWithObject:)
instancję MyObject, wykonaj następujące czynności:źródło