Muszę ukryć (uczynić prywatną) -init
metodę mojej klasy w Objective-C.
Jak mogę to zrobić?
objective-c
lajos
źródło
źródło
NS_UNAVAILABLE
. Generalnie zachęcałbym do skorzystania z tego podejścia. Czy PO rozważyłby zmianę przyjętej odpowiedzi? Pozostałe odpowiedzi zawierają wiele przydatnych szczegółów, ale nie są preferowaną metodą osiągnięcia tego.NS_UNAVAILABLE
nadal umożliwia wywołującemu wywołanieinit
pośrednie przeznew
. Zwykłe zastąpienieinit
powrotunil
będzie obsługiwać oba przypadki.Odpowiedzi:
Objective-C, podobnie jak Smalltalk, nie ma koncepcji metod „prywatnych” i „publicznych”. W dowolnym momencie można wysłać dowolną wiadomość do dowolnego obiektu.
Co możesz zrobić, to
NSInternalInconsistencyException
zgłosić, jeśli twoja-init
metoda jest wywoływana:Inną alternatywą - która prawdopodobnie jest znacznie lepsza w praktyce - jest
-init
zrobienie czegoś rozsądnego dla klasy, jeśli to w ogóle możliwe.Jeśli próbujesz to zrobić, ponieważ próbujesz „upewnić się”, że używany jest obiekt pojedynczy, nie przejmuj się. W szczególności, nie przejmuj się z „ręcznym
+allocWithZone:
,-init
,-retain
,-release
” metoda tworzenia pojedynczych. Jest to praktycznie zawsze niepotrzebne i po prostu powoduje komplikacje bez żadnej znaczącej korzyści.Zamiast tego po prostu napisz swój kod w taki sposób, aby Twoja
+sharedWhatever
metoda była sposobem uzyskiwania dostępu do singletona i udokumentuj to jako sposób na uzyskanie pojedynczej instancji w nagłówku. To powinno wystarczyć w większości przypadków.źródło
alloc
iinit
i mają funkcję kodu nieprawidłowo, ponieważ mają oni prawo klasy, ale źle instancji. Na tym polega istota zasady hermetyzacji w OO. Ukrywasz w swoich interfejsach API rzeczy, których inne klasy nie powinny potrzebować ani uzyskiwać do nich dostępu. Nie chcesz po prostu upubliczniać wszystkiego i oczekujesz, że ludzie będą to wszystko śledzić.NS_UNAVAILABLE
To jest krótka wersja niedostępnego atrybutu. Po raz pierwszy pojawił się w macOS 10.7 i iOS 5 . Jest zdefiniowany w NSObjCRuntime.h jako
#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
.Istnieje wersja, która wyłącza tę metodę tylko dla klientów Swift , a nie dla kodu ObjC:
unavailable
Dodaj
unavailable
atrybut do nagłówka, aby wygenerować błąd kompilatora przy każdym wywołaniu init.Jeśli nie masz powodu, po prostu wpisz
__attribute__((unavailable))
, a nawet__unavailable
:doesNotRecognizeSelector:
Służy
doesNotRecognizeSelector:
do zgłaszania wyjątku NSInvalidArgumentException. „System wykonawczy wywołuje tę metodę za każdym razem, gdy obiekt otrzyma wiadomość aSelector, na którą nie może odpowiedzieć ani przesłać dalej”.NSAssert
Służy
NSAssert
do zgłaszania wyjątku NSInternalInconsistencyException i wyświetlania komunikatu:raise:format:
Użyj,
raise:format:
aby zgłosić własny wyjątek:[self release]
jest potrzebne, ponieważ obiekt był jużalloc
zajęty. Podczas korzystania z ARC kompilator zadzwoni za Ciebie. W każdym razie nie ma się czym martwić, gdy zamierzasz celowo przerwać wykonanie.objc_designated_initializer
W przypadku, gdy zamierzasz wyłączyć,
init
aby wymusić użycie wyznaczonego inicjatora, istnieje odpowiedni atrybut:To generuje ostrzeżenie, chyba że jakakolwiek inna metoda inicjowania wywoła
myOwnInit
wewnętrznie. Szczegóły zostaną opublikowane w Adopting Modern Objective-C po następnym wydaniu Xcode (tak sądzę).źródło
init
. Skoro jeśli ta metoda jest nieprawidłowa, to dlaczego inicjalizujesz obiekt? Dodatkowo, rzucając wyjątek, będziesz mógł określić niestandardową wiadomość przekazującą programistę poprawnąinit*
metodę, podczas gdy nie masz takiej opcji w przypadkudoesNotRecognizeSelector
.Firma Apple zaczęła używać następujących plików nagłówkowych w celu wyłączenia konstruktora inicjującego:
To poprawnie wyświetla się jako błąd kompilatora w Xcode. W szczególności jest to ustawione w kilku ich plikach nagłówkowych HealthKit (jednym z nich jest HKUnit).
źródło
Jeśli mówisz o domyślnej metodzie -init, nie możesz. Jest dziedziczony z NSObject i każda klasa odpowie na to bez żadnych ostrzeżeń.
Możesz utworzyć nową metodę, powiedz -initMyClass i umieścić ją w prywatnej kategorii, jak sugeruje Matt. Następnie zdefiniuj domyślną metodę -init, aby zgłosić wyjątek, jeśli jest wywoływana lub (lepiej) wywołać prywatną -initMyClass z pewnymi wartościami domyślnymi.
Jednym z głównych powodów, dla których ludzie wydają się chcieć ukryć init, są pojedyncze obiekty . W takim przypadku nie musisz ukrywać -init, po prostu zwróć obiekt singleton (lub utwórz go, jeśli jeszcze nie istnieje).
źródło
Umieść to w pliku nagłówkowym
źródło
Możesz zadeklarować niedostępność dowolnej metody za pomocą
NS_UNAVAILABLE
.Możesz więc umieścić te linie poniżej swojego @interfejsu
Jeszcze lepiej zdefiniuj makro w nagłówku prefiksu
i
źródło
To zależy od tego, co masz na myśli, mówiąc „ustaw jako prywatny”. W Objective-C wywołanie metody na obiekcie można lepiej opisać jako wysłanie wiadomości do tego obiektu. W języku nie ma nic, co zabrania klientowi wywoływania jakiejkolwiek metody na obiekcie; najlepsze co możesz zrobić to nie zadeklarować metody w pliku nagłówkowym. Jeśli klient mimo wszystko wywoła metodę „prywatną” z odpowiednim podpisem, będzie ona nadal wykonywana w czasie wykonywania.
To powiedziawszy, najczęstszym sposobem tworzenia prywatnej metody w Objective-C jest utworzenie Kategorii w pliku implementacji i zadeklarowanie wszystkich „ukrytych” tam metod. Pamiętaj, że to naprawdę nie zapobiegnie
init
uruchomieniu wywołań do , ale kompilator wypluje ostrzeżenia, jeśli ktoś spróbuje to zrobić.MyClass.m
Na MacRumors.com jest przyzwoity wątek na ten temat.
źródło
Cóż, problem dlaczego nie możesz uczynić go "prywatnym / niewidocznym" jest taki, że metoda init zostaje wysłana na id (alokacja zwraca id) a nie do YourClass
Zauważ, że z punktu kompilatora (kontrolera) identyfikator może potencjalnie odpowiedzieć na wszystko, co kiedykolwiek zostanie wpisane (nie może sprawdzić, co naprawdę trafia do identyfikatora w czasie wykonywania), więc możesz ukryć init tylko wtedy, gdy nic nigdzie tego nie zrobi (publicly = in header) używaj metody init, niż kompilator wiedziałby, że nie ma sposobu, aby id odpowiedział na init, ponieważ nigdzie nie ma init (w twoim źródle, we wszystkich bibliotekach itp.)
więc nie możesz zabronić użytkownikowi przekazywania init i zostania zniszczonym przez kompilator ... ale co możesz zrobić, to uniemożliwić użytkownikowi uzyskanie prawdziwej instancji przez wywołanie init
po prostu implementując init, który zwraca nil i ma (prywatny / niewidoczny) inicjator, którego nazwy ktoś inny nie otrzyma (jak initOnce, initWithSpecial ...)
Uwaga: ktoś mógłby to zrobić
i faktycznie zwróciłoby nową instancję, ale gdyby initOnce nigdzie w naszym projekcie nie byłby publicznie zadeklarowany (w nagłówku), wygenerowałoby ostrzeżenie (id może nie odpowiedzieć ...) i tak czy inaczej osoba używająca tego potrzebowałaby wiedzieć dokładnie, że prawdziwym inicjatorem jest initOnce
moglibyśmy temu zapobiec, ale nie ma takiej potrzeby
źródło
Muszę wspomnieć, że umieszczanie asercji i wywoływanie wyjątków w celu ukrycia metod w podklasie ma paskudną pułapkę na dobrze zamierzone.
Poleciłbym użyć tego,
__unavailable
co wyjaśnił Jano w swoim pierwszym przykładzie .Metody można przesłonić w podklasach. Oznacza to, że jeśli metoda w nadklasie używa metody, która po prostu zgłasza wyjątek w podklasie, prawdopodobnie nie będzie działać zgodnie z przeznaczeniem. Innymi słowy, właśnie zepsułeś to, co kiedyś działało. Dotyczy to również metod inicjalizacji. Oto przykład takiej dość powszechnej realizacji:
Wyobraź sobie, co się stanie z -initWithLessParameters, jeśli zrobię to w podklasie:
Oznacza to, że powinieneś mieć tendencję do używania metod prywatnych (ukrytych), szczególnie w metodach inicjalizacji, chyba że planujesz nadpisać metody. Ale to jest inny temat, ponieważ nie zawsze masz pełną kontrolę nad implementacją superklasy. (To sprawia, że kwestionuję użycie __attribute ((objc_designated_initializer)) jako złej praktyki, chociaż nie używałem go dogłębnie.)
Oznacza to również, że można używać potwierdzeń i wyjątków w metodach, które muszą zostać przesłonięte w podklasach. (Metody „abstrakcyjne” jak w Tworzenie klasy abstrakcyjnej w Objective-C )
I nie zapomnij o + nowej metodzie klasy.
źródło