Słyszałem, jak ludzie twierdzą, że zamiana metod jest niebezpieczną praktyką. Nawet nazwa swizzling sugeruje, że to trochę oszustwo.
Metoda Swizzling modyfikuje mapowanie, tak aby wywołanie selektora A faktycznie wywołało implementację B. Jednym z zastosowań tego jest rozszerzenie zachowania zamkniętych klas źródłowych.
Czy możemy sformalizować ryzyko, aby każdy, kto decyduje się na użycie swizzling, mógł podjąć świadomą decyzję, czy warto za to, co próbuje zrobić.
Na przykład
- Nazewnictwo kolizji : jeśli klasa rozszerzy później swoją funkcjonalność o dodaną nazwę metody, spowoduje to wiele problemów. Zmniejsz ryzyko, rozsądnie nazywając zamiecione metody.
ios
objective-c
swizzling
Robert
źródło
źródło
Odpowiedzi:
Myślę, że to naprawdę świetne pytanie i szkoda, że zamiast zmierzyć się z prawdziwym pytaniem, większość odpowiedzi omijała ten problem i po prostu mówiła, żeby nie używać swizzlingu.
Używanie metody skwierczenie jest jak używanie ostrych noży w kuchni. Niektórzy boją się ostrych noży, ponieważ myślą, że źle się skaleczą, ale prawda jest taka, że ostre noże są bezpieczniejsze .
Metoda swizzling może służyć do pisania lepszego, wydajniejszego i łatwiejszego do utrzymania kodu. Można go również nadużywać i prowadzić do okropnych błędów.
tło
Podobnie jak w przypadku wszystkich wzorców projektowych, jeśli jesteśmy w pełni świadomi konsekwencji tego wzoru, jesteśmy w stanie podejmować bardziej świadome decyzje dotyczące tego, czy go użyć. Singletony są dobrym przykładem czegoś, co jest dość kontrowersyjne, i nie bez powodu - są naprawdę trudne do prawidłowego wdrożenia. Jednak wiele osób nadal decyduje się na użycie singletonów. To samo można powiedzieć o swizzlingu. Powinieneś sformułować własną opinię, gdy w pełni zrozumiesz zarówno dobre, jak i złe.
Dyskusja
Oto niektóre z pułapek metody swizzling:
Wszystkie te punkty są ważne, a zajmując się nimi, możemy poprawić zarówno nasze rozumienie metody swizzling, jak i metodologię stosowaną do osiągnięcia wyniku. Wezmę każdy na raz.
Metoda swizzling nie jest atomowa
Nie widziałem jeszcze implementacji metody zamiatania, którą można bezpiecznie stosować jednocześnie 1 . W rzeczywistości nie stanowi to problemu w 95% przypadków, w których chcesz użyć metody swizzling. Zwykle po prostu chcesz zastąpić implementację metody i chcesz, aby ta implementacja była używana przez cały okres istnienia programu. Oznacza to, że powinieneś wykonać swoją metodę zamiatania
+(void)load
. Metodaload
klasy jest wykonywana szeregowo na początku aplikacji. Nie będziesz mieć żadnych problemów ze współbieżnością, jeśli wykonasz swizzling tutaj. Jeśli miałbyś się w to wpakować+(void)initialize
jednak wdarł się do niego, mógłbyś skończyć z warunkami wyścigu w swizzlingowej implementacji, a środowisko wykonawcze mogłoby skończyć się w dziwnym stanie.Zmienia zachowanie nieposiadanego kodu
Jest to problem z zamiataniem, ale o to w tym wszystkim chodzi. Celem jest możliwość zmiany tego kodu. Powodem, dla którego ludzie wskazują, że jest to wielka sprawa, jest to, że nie tylko zmieniasz rzeczy dla jednego wystąpienia
NSButton
, dla którego chcesz to zmienić, ale zamiast tego dla wszystkichNSButton
wystąpień w aplikacji. Z tego powodu powinieneś zachować ostrożność podczas zamiatania, ale nie musisz tego całkowicie unikać.Pomyśl o tym w ten sposób ... jeśli przesłonisz metodę w klasie i nie wywołasz metody superklasy, możesz spowodować problemy. W większości przypadków superklasa oczekuje wywołania tej metody (chyba że udokumentowano inaczej). Jeśli zastosujesz tę samą myśl do swizzling, omówiłeś większość problemów. Zawsze nazywaj oryginalną implementację. Jeśli nie, prawdopodobnie zmieniasz zbyt wiele, aby być bezpiecznym.
Możliwe konflikty nazw
Konflikty nazw są problemem w całej Kakao. Często poprzedzamy nazwy klas i nazwy metod w kategoriach. Niestety konflikty nazw są plagą w naszym języku. Jednak w przypadku zamiatania nie muszą tak być. Musimy tylko zmienić sposób, w jaki myślimy o zamianie metod. Większość zamiatania odbywa się w następujący sposób:
Działa to dobrze, ale co by się stało, gdyby
my_setFrame:
zostało zdefiniowane gdzie indziej? Ten problem nie jest unikalny dla swizzowania, ale i tak możemy go obejść. To obejście ma również dodatkową zaletę rozwiązania innych pułapek. Oto, co robimy zamiast tego:Chociaż wygląda to nieco mniej niż Objective-C (ponieważ używa wskaźników funkcji), pozwala uniknąć konfliktów nazw. Zasadniczo robi dokładnie to samo, co standardowe swizzling. Może to być niewielka zmiana dla osób, które używają swizzlingu, jak to zostało zdefiniowane przez jakiś czas, ale ostatecznie myślę, że jest lepiej. Metoda swizzling jest zdefiniowana w następujący sposób:
Przesuwanie przez zmianę nazw metod zmienia argumenty metody
To jest moja wielka myśl. Jest to powód, dla którego nie należy wykonywać standardowej zamiany metody. Zmieniasz argumenty przekazane do implementacji oryginalnej metody. Tak się dzieje:
Ta linia to:
Który użyje środowiska wykonawczego do sprawdzenia implementacji
my_setFrame:
. Po znalezieniu implementacji wywołuje implementację z tymi samymi podanymi argumentami. Implementacja, którą znajduje, jest oryginalną implementacjąsetFrame:
, więc idzie do przodu i nazywa to, ale_cmd
argument nie jest taki,setFrame:
jak powinien. Jest terazmy_setFrame:
. Oryginalna implementacja jest wywoływana z argumentem, którego nigdy się nie spodziewała. To nie jest dobre.Istnieje proste rozwiązanie - skorzystaj z alternatywnej techniki zamiatania zdefiniowanej powyżej. Argumenty pozostaną niezmienione!
Kolejność zamiatania ma znaczenie
Kolejność zamiatania metod ma znaczenie. Zakładając, że
setFrame:
zdefiniowano tylkoNSView
, wyobraź sobie następującą kolejność rzeczy:Co dzieje się, gdy włączona
NSButton
jest metoda? Cóż, większość swizzling zapewni, że nie zastąpi implementacjisetFrame:
dla wszystkich widoków, więc wyciągnie metodę instancji. Spowoduje to wykorzystanie istniejącej implementacji do ponownego zdefiniowaniasetFrame:
wNSButton
klasie, aby wymiana implementacji nie wpływała na wszystkie widoki. Istniejąca implementacja jest zdefiniowana naNSView
. To samo stanie się po włączeniuNSControl
(ponownie przy użyciuNSView
implementacji).Kiedy
setFrame:
wciśniesz przycisk, wywoła on twoją metodę swizzled, a następnie przeskoczy prosto dosetFrame:
metody pierwotnie zdefiniowanejNSView
. TeNSControl
iNSView
swizzled implementacje nie zostanie wywołana.Ale co gdyby zamówienie było:
Ponieważ zamiatanie widoku odbywa się jako pierwsze, zamiatanie kontrolne będzie w stanie wyciągnąć właściwą metodę. Podobnie, ponieważ swizzling kontrola była przed przycisk swizzling, przycisk będzie podciągnąć swizzled realizację kontrolki z
setFrame:
. Jest to trochę mylące, ale jest to właściwa kolejność. Jak możemy zapewnić ten porządek rzeczy?Ponownie, użyj tylko
load
do zamiatania rzeczy. Jeśli wślizgniesz sięload
i wprowadzisz tylko zmiany w ładowanej klasie, będziesz bezpieczny. Teload
gwarancje metoda, super sposób obciążenie klasa zostanie wezwany przed wszelkimi podklasy. Otrzymamy dokładne zamówienie!Trudne do zrozumienia (wygląda rekurencyjnie)
Patrząc na tradycyjnie zdefiniowaną metodę zamiatania, myślę, że naprawdę trudno powiedzieć, co się dzieje. Ale patrząc na alternatywny sposób, w jaki zrobiliśmy swizzling powyżej, jest to dość łatwe do zrozumienia. Ten już został rozwiązany!
Trudne do debugowania
Jedną z nieporozumień podczas debugowania jest dziwny ślad, w którym zmieszane nazwy są pomieszane, a wszystko grzęźnie w twojej głowie. Ponownie, alternatywne wdrożenie rozwiązuje ten problem. Zobaczysz wyraźnie nazwane funkcje w śladach. Mimo to zamiatanie może być trudne do debugowania, ponieważ trudno jest zapamiętać, jaki wpływ ma zamiatanie. Dobrze udokumentuj swój kod (nawet jeśli uważasz, że jesteś jedynym, który go zobaczy). Postępuj zgodnie z dobrymi praktykami, a wszystko będzie dobrze. Debugowanie nie jest trudniejsze niż kod wielowątkowy.
Wniosek
Metoda swizzling jest bezpieczna, jeśli jest właściwie stosowana. Prostym środkiem bezpieczeństwa, jaki możesz podjąć, jest tylko włożenie się
load
. Podobnie jak wiele rzeczy w programowaniu, może być niebezpieczny, ale zrozumienie konsekwencji pozwoli na prawidłowe korzystanie z niego.1 Używając wyżej zdefiniowanej metody swizzling, możesz sprawić, że nici będą bezpieczne, jeśli będziesz używał trampolin. Potrzebujesz dwóch trampolin. Na początku metody należy przypisać wskaźnik funkcji
store
do funkcji, która obraca się do momentu zmiany adresu, na którystore
wskazuje. Pozwoliłoby to uniknąć warunków wyścigu, w których wywołano metodę swizzled, zanim można było ustawićstore
wskaźnik funkcji. Będziesz wtedy musiał użyć trampoliny w przypadku, gdy implementacja nie jest jeszcze zdefiniowana w klasie i przeszukaj trampolinę i poprawnie wywołaj metodę superklasy. Zdefiniowanie metody w taki sposób, aby dynamicznie wyszukiwała super implementację, zapewni, że kolejność wywoływania połączeń nie ma znaczenia.źródło
Najpierw zdefiniuję dokładnie, co mam na myśli przez metodę swizzling:
Metoda swizzling jest bardziej ogólna niż ta, ale interesuje mnie to.
Niebezpieczeństwa
Zmiany w oryginalnej klasie . Nie jesteśmy właścicielami klasy, którą zamiatamy. Jeśli klasa się zmieni, nasz swizzle może przestać działać.
Trudne w utrzymaniu . Nie tylko musisz pisać i utrzymywać metodę swizzled. musisz napisać i utrzymywać kod, który wykonuje swizzle
Trudno debugować . Trudno jest śledzić przepływ swizzle, niektórzy ludzie mogą nawet nie zdawać sobie sprawy, że swizzle został już wykonany. Jeśli pojawią się błędy ze swizzle (być może spowodowane zmianami w oryginalnej klasie), będzie trudno je rozwiązać.
Podsumowując, powinieneś ograniczyć swizzling do minimum i zastanów się, jak zmiany w oryginalnej klasie mogą wpłynąć na twój swizzle. Powinieneś także wyraźnie komentować i dokumentować to, co robisz (lub po prostu całkowicie tego unikać).
źródło
To nie samo wirowanie jest naprawdę niebezpieczne. Problem polega na tym, że, jak mówisz, często używany jest do modyfikowania zachowania klas frameworka. Zakłada się, że wiesz coś o tym, jak działają te prywatne klasy, co jest „niebezpieczne”. Nawet jeśli twoje modyfikacje działają dzisiaj, zawsze istnieje szansa, że Apple zmieni klasę w przyszłości i spowoduje, że twoja modyfikacja się zepsuje. Ponadto, jeśli robi to wiele różnych aplikacji, Apple znacznie utrudnia zmianę frameworka bez niszczenia wielu istniejących programów.
źródło
Używany ostrożnie i mądrze, może prowadzić do eleganckiego kodu, ale zwykle prowadzi do mylącego kodu.
Mówię, że należy go zbanować, chyba że wiesz, że stanowi on bardzo elegancką okazję do wykonania określonego zadania projektowego, ale musisz wyraźnie wiedzieć, dlaczego dobrze pasuje do sytuacji i dlaczego alternatywy nie działają elegancko w tej sytuacji .
Np. Jednym dobrym zastosowaniem metody swizzling jest swizzling, w ten sposób ObjC implementuje Key Value Observing.
Zły przykład może polegać na zamianie metod jako sposobu na rozszerzenie twoich klas, co prowadzi do bardzo wysokiego sprzężenia.
źródło
Chociaż użyłem tej techniki, chciałbym zauważyć, że:
źródło
Uważam, że największym niebezpieczeństwem jest tworzenie wielu niepożądanych efektów ubocznych, zupełnie przypadkowo. Te działania niepożądane mogą przedstawiać się jako „błędy”, które z kolei prowadzą cię na niewłaściwą ścieżkę do znalezienia rozwiązania. Z mojego doświadczenia wynika, że niebezpieczeństwo jest nieczytelne, zagmatwane i frustrujące. To tak, jakby ktoś nadużywał wskaźników funkcji w C ++.
źródło
Możesz skończyć z dziwnie wyglądającym kodem
z rzeczywistego kodu produkcyjnego związanego z magią interfejsu użytkownika.
źródło
Metoda swizzling może być bardzo pomocna w testach jednostkowych.
Pozwala napisać próbny obiekt i użyć tego próbnego obiektu zamiast rzeczywistego obiektu. Twój kod powinien pozostać czysty, a test jednostkowy ma przewidywalne zachowanie. Powiedzmy, że chcesz przetestować kod korzystający z CLLocationManager. Twój test jednostkowy mógłby zamienić startUpdatingLocation tak, aby dostarczał delegatowi z góry określony zestaw lokalizacji, a Twój kod nie musiałby się zmieniać.
źródło