Muszę ukryć (uczynić prywatną) -init
metodę mojej klasy w Objective-C.
Jak mogę to zrobić?
NS_UNAVAILABLE
nadal umożliwia wywołującemu wywołanie init
pośrednie przez new
. Zwykłe zastąpienie init
powrotu nil
będzie obsługiwać oba przypadki.
Muszę ukryć (uczynić prywatną) -init
metodę mojej klasy w Objective-C.
Jak mogę to zrobić?
NS_UNAVAILABLE
nadal umożliwia wywołującemu wywołanie init
pośrednie przez new
. Zwykłe zastąpienie init
powrotu nil
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:
- (id)init {
[self release];
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"-init is not a valid initializer for the class Foo"
userInfo:nil];
return nil;
}
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.
alloc
i init
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
- (instancetype)init 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:
- (instancetype)init NS_SWIFT_UNAVAILABLE;
unavailable
Dodaj unavailable
atrybut do nagłówka, aby wygenerować błąd kompilatora przy każdym wywołaniu init.
-(instancetype) init __attribute__((unavailable("init not available")));
Jeśli nie masz powodu, po prostu wpisz __attribute__((unavailable))
, a nawet __unavailable
:
-(instancetype) __unavailable init;
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”.
- (instancetype) init {
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
NSAssert
Służy NSAssert
do zgłaszania wyjątku NSInternalInconsistencyException i wyświetlania komunikatu:
- (instancetype) init {
[self release];
NSAssert(false,@"unavailable, use initWithBlah: instead");
return nil;
}
raise:format:
Użyj, raise:format:
aby zgłosić własny wyjątek:
- (instancetype) init {
[self release];
[NSException raise:NSGenericException
format:@"Disabled. Use +[[%@ alloc] %@] instead",
NSStringFromClass([self class]),
NSStringFromSelector(@selector(initWithStateDictionary:))];
return nil;
}
[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:
-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;
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ę).
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 przypadku doesNotRecognizeSelector
.
Firma Apple zaczęła używać następujących plików nagłówkowych w celu wyłączenia konstruktora inicjującego:
- (instancetype)init NS_UNAVAILABLE;
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).
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).
Umieść to w pliku nagłówkowym
- (id)init UNAVAILABLE_ATTRIBUTE;
Możesz zadeklarować niedostępność dowolnej metody za pomocą NS_UNAVAILABLE
.
Możesz więc umieścić te linie poniżej swojego @interfejsu
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
Jeszcze lepiej zdefiniuj makro w nagłówku prefiksu
#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;
i
@interface YourClass : NSObject
NO_INIT
// Your properties and messages
@end
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
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
Na MacRumors.com jest przyzwoity wątek na ten temat.
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 ...)
static SomeClass * SInstance = nil;
- (id)init
{
// possibly throw smth. here
return nil;
}
- (id)initOnce
{
self = [super init];
if (self) {
return self;
}
return nil;
}
+ (SomeClass *) shared
{
if (nil == SInstance) {
SInstance = [[SomeClass alloc] initOnce];
}
return SInstance;
}
Uwaga: ktoś mógłby to zrobić
SomeClass * c = [[SomeClass alloc] initOnce];
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
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:
- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
...bla bla...
return self;
}
- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
return self;
}
Wyobraź sobie, co się stanie z -initWithLessParameters, jeśli zrobię to w podklasie:
- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
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.
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.