Mam ogólne pytanie dotyczące pisania metod init w Objective-C.
Widzę wszędzie (kod Apple, książki, kod open source itp.), Że metoda init powinna sprawdzać, czy self = [super init] nie jest zerowe przed kontynuacją inicjalizacji.
Domyślny szablon Apple dla metody init to:
- (id) init
{
self = [super init];
if (self != nil)
{
// your code here
}
return self;
}
Czemu?
Mam na myśli, kiedy init kiedykolwiek zwróci zero? Jeśli wywołałem init na NSObject i otrzymałem zero, to coś musi być naprawdę spieprzone, prawda? W takim przypadku równie dobrze możesz nawet nie napisać programu ...
Czy to naprawdę takie częste, że metoda init klasy może zwracać zero? Jeśli tak, w jakim przypadku i dlaczego?
objective-c
null
init
Jasarien
źródło
źródło
Odpowiedzi:
Na przykład:
... i tak dalej. (Uwaga: NSData może zgłosić wyjątek, jeśli plik nie istnieje). Istnieje wiele obszarów, w których zwracanie zera jest oczekiwanym zachowaniem, gdy pojawia się problem, dlatego standardową praktyką jest sprawdzanie zera prawie przez cały czas, dla zachowania spójności.
źródło
Ten konkretny idiom jest standardowy, ponieważ działa we wszystkich przypadkach.
Chociaż jest to rzadkie, będą przypadki, w których ...
... zwraca inną instancję, wymagając w ten sposób przypisania do siebie.
Będą przypadki, w których zwróci wartość nil, wymagając w ten sposób sprawdzenia nil, aby kod nie próbował zainicjować gniazda zmiennej instancji, która już nie istnieje.
Najważniejsze jest to, że jest to udokumentowany prawidłowy wzorzec do użycia, a jeśli go nie używasz, robisz to źle.
źródło
-init
problemów…)[super init]
zwraca zero, gdy bezpośrednia nadklasa jestNSObject
? Czy nie jest to przypadek, w którym „wszystko jest zepsute”?NSObject
jest bezpośrednią nadklasą. Ale ... nawet jeśli zadeklarujeszNSObject
jako bezpośrednią nadklasę, coś mogło zostać zmodyfikowane w czasie wykonywania w taki sposób, żeNSObject
implementacjainit
nie jest tym, co faktycznie się nazywa.Myślę, że w większości klas, jeśli wartość zwracana przez [super init] jest równa zero i sprawdzasz to, zgodnie ze standardowymi praktykami, a następnie zwracasz przedwcześnie, jeśli zero, w zasadzie Twoja aplikacja nadal nie będzie działać poprawnie. Jeśli myślisz o tym, mimo że jeśli (self! = Nil) sprawdzenie czy istnieje, do prawidłowego działania swojej klasie, 99,99% czasu faktycznie zrobić potrzebujemy siebie być non-zero. Teraz załóżmy, że, niezależnie od przyczyny, [SUPER startowych] zrobił zwrot nil, w zasadzie czek na zero jest w zasadzie przekazując złotówki do rozmówcy swojej klasie, gdzie byłoby to prawdopodobnie nie tak czy inaczej, ponieważ będzie on oczywiście założyć, że wezwanie było odnoszący sukcesy.
Zasadniczo rozumiem to, że w 99,99% przypadków if (self! = Nil) nie kupuje niczego w kategoriach większej odporności, ponieważ po prostu przekazujesz pieniądze inwokatorowi. Aby naprawdę móc sobie z tym poradzić, należałoby wprowadzić kontrole w całej hierarchii wywołań. I nawet wtedy jedyne, co by ci kupiło, to to, że twoja aplikacja zawiedzie trochę bardziej czysto / solidnie. Ale i tak by się nie udało.
Jeśli klasa biblioteki arbitralnie zdecydowała się zwrócić nil w wyniku [superinita], to i tak jesteś prawie spieprzony, a to bardziej wskazuje na to, że autor klasy biblioteki popełnił błąd implementacji.
Myślę, że jest to bardziej sugestia dotycząca starszego kodowania, gdy aplikacje działały w znacznie bardziej ograniczonej pamięci.
Ale w przypadku kodu poziomu C nadal zazwyczaj sprawdzałbym wartość zwracaną przez malloc () względem wskaźnika NULL. Natomiast w przypadku Objective-C, dopóki nie znajdę dowodów przeciwnych, myślę, że generalnie pomijam sprawdzanie if (self! = Nil). Skąd ta rozbieżność?
Ponieważ na poziomach C i Malloc w niektórych przypadkach można częściowo odzyskać zdrowie. Podczas gdy myślę, że w Objective-C, w 99,99% przypadków, jeśli [super init] zwraca zero, w zasadzie jesteś spieprzony, nawet jeśli spróbujesz sobie z tym poradzić. Równie dobrze możesz po prostu pozwolić aplikacji się zawiesić i poradzić sobie z następstwami.
źródło
To jest swego rodzaju podsumowanie powyższych uwag.
Powiedzmy, że powraca superklasa
nil
. Co się stanie?Jeśli nie przestrzegasz konwencji
Twój kod ulegnie awarii w środku
init
metody. (chyba żeinit
nie robi nic istotnego)Jeśli zastosujesz się do konwencji, nie wiedząc, że superklasa może zwrócić zero (większość ludzi kończy tutaj)
Twój kod prawdopodobnie ulegnie awarii w pewnym momencie później, ponieważ Twoja instancja jest
nil
tam, gdzie spodziewałeś się czegoś innego. Albo twój program będzie zachowywał się nieoczekiwanie bez awarii. O jej! Chcesz tego? Nie wiem...Jeśli będziesz postępować zgodnie z konwencjami, dobrowolnie pozwalając swojej podklasie zwrócić zero
Twoja dokumentacja kodu (!) Powinna jasno określać: „return ... or nil”, a reszta kodu musi być przygotowana do obsługi tego. Teraz to ma sens.
źródło
Zazwyczaj, jeśli twoja klasa pochodzi bezpośrednio z
NSObject
, nie musisz tego robić. Jest to jednak dobry nawyk, ponieważ jeśli twoja klasa pochodzi od innych klas, ich inicjatory mogą powrócićnil
, a jeśli tak, twój inicjator może następnie przechwycić to i zachować się poprawnie.I tak, dla przypomnienia, postępuję zgodnie z najlepszymi praktykami i piszę je na wszystkich moich zajęciach, nawet tych pochodzących bezpośrednio z
NSObject
.źródło
Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
NSObject
nie gwarantuje również, że-init
da ci toNSObject
, jeśli liczysz egzotyczne środowiska wykonawcze, takie jak starsze wersje GNUstep w (który zwracaGSObject
), więc bez względu na wszystko, czek i przypisanie.Masz rację, często mógłbyś po prostu napisać
[super init]
, ale to nie zadziała dla podklasy czegokolwiek. Ludzie wolą po prostu zapamiętać jedną standardową linię kodu i używać jej cały czas, nawet wtedy, gdy jest to tylko czasami konieczne, i tak otrzymujemy standardif (self = [super init])
, który zakłada zarówno możliwość zwrócenia zera, jak i możliwość zwrócenia obiektu innego niżself
zwrócenie na konto.źródło
Częstym błędem jest pisanie
która zwraca instancję nadklasy, która NIE jest tym, czego chcesz w konstruktorze / inicie podklasy. Otrzymujesz obiekt, który nie reaguje na metody podklasy, co może być mylące i generuje mylące błędy dotyczące braku odpowiedzi na metody lub identyfikatory, które nie zostały znalezione itp.
jest potrzebne, jeśli superklasa ma elementy składowe (zmienne lub inne obiekty) do zainicjowania najpierw przed skonfigurowaniem elementów składowych podklas. W przeciwnym razie środowisko uruchomieniowe objc inicjuje je wszystkie na 0 lub na zero . (w przeciwieństwie do ANSI C, który często przydziela fragmenty pamięci bez ich czyszczenia )
I tak, inicjalizacja klasy bazowej może się nie powieść z powodu błędów braku pamięci, brakujących komponentów, błędów pozyskiwania zasobów itp., Więc sprawdzenie zeru jest rozsądne i zajmuje mniej niż kilka milisekund.
źródło
Ma to na celu sprawdzenie, czy intialazacja zadziałała, instrukcja if zwraca prawdę, jeśli metoda init nie zwróciła nil, więc jest to sposób na sprawdzenie, czy tworzenie obiektu działa poprawnie. Niewiele powodów, dla których mogę pomyśleć, że init może się nie powieść, być może jest to nadpisana metoda init, o której superklasa nie wie, lub coś w tym rodzaju, chociaż nie sądziłbym, że jest to tak powszechne. Ale jeśli tak się stanie, lepiej, żeby nic się nie wydarzyło, niż przypuszczam awarię, więc zawsze jest sprawdzane ...
źródło
W OS X nie jest tak prawdopodobne,
-[NSObject init]
że zawiedzie z powodu pamięci. Tego samego nie można powiedzieć o iOS.Ponadto dobrą praktyką jest pisanie podczas tworzenia podklas klasy, która może powrócić
nil
z dowolnego powodu.źródło
-[NSObject init]
jest bardzo mało prawdopodobne, aby zakończyło się niepowodzeniem ze względu na pamięć, ponieważ nie przydziela żadnej pamięci.