2011-09-01 15 views
16

Czytałem kilka niezwykłych zasobów na singletons w Obj-C:W jaki sposób Objective-C singleton powinien implementować metodę init?

  1. SO pytanie: What does your Objective-C singleton look like?
  2. piątek Q & A: Care and Feeding of Singletons
  3. Jabłko dokumentów: Creating a Singleton Instance

ale żaden z tych zasobów adresowane init koncepcja metody jawnie i choć wciąż jest nov lód do Obj-C Jestem zdezorientowany, jak powinienem go wdrożyć.

tej pory wiem, że posiadanie init prywatny nie jest możliwe w Obj-C, ponieważ nie oferują prawdziwe metod prywatnych ... więc możliwe, że użytkownik może zadzwonić [[MyClass alloc] init] zamiast za pomocą mojego [MyClass sharedInstance].

Jakie są pozostałe opcje? Uważam, że powinienem również poradzić sobie z podklasowymi scenariuszami mojego singletonu. Nie ma to wpływu na metodę

Odpowiedz

25

Cóż, łatwym sposobem obejścia init jest po prostu nie pisać, aby wywołać domyślną implementację NSObject (która zwraca tylko self). Następnie, dla swojej funkcji sharedInstance, zdefiniuj i wywołaj prywatną funkcję, która wykonuje pracę podobną do init podczas tworzenia singletonu. (Pozwala to uniknąć przypadkowego ponownego zainicjowania Twojego singletonu przez użytkownika.)

Jednak !!! Główny problem polega na tym, że użytkownik alloc jest wywoływany przez użytkownika twojego kodu! Do tego ja osobiście polecam trasę Apple z nadrzędnego allocWithZone: ...

+ (id)allocWithZone:(NSZone *)zone 
{ 
    return [[self sharedInstance] retain]; 
} 

Oznacza to, że użytkownik będzie nadal uzyskać singleton instancji i mogą błędnie używać jak gdyby przydzielono go i bezpiecznie zwolnić go kiedyś od tego custom alloc wykonuje retain na singleton. (Uwaga: alloc dzwoni allocWithZone: i nie trzeba go oddzielnie zastępować.)

Nadzieję, że pomaga! Daj mi znać, jeśli chcesz uzyskać więcej informacji ~

EDIT: Rozszerzanie odpowiedź dostarczenie przykład i więcej szczegółów -

Biorąc odpowiedź Catfish_Man jest pod uwagę, że to często nie ważne, aby stworzyć kuloodporną Singleton, a zamiast tego po prostu napisz jakieś sensowne komentarze w nagłówkach/dokumentacji i umieść je w assert.

Jednak w moim przypadku chciałem mieć bezpieczny wątek typu single-leniwy - to znaczy, że nie jest przydzielany do momentu użycia, zamiast automatycznego przydzielania przy uruchomieniu aplikacji. Po tym, jak nauczyłem się tego robić bezpiecznie, pomyślałem, że równie dobrze mogę z nim iść.

EDIT # 2: Teraz używam GCD na dispatch_once(...) dla podejścia wątku bezpieczny alokacji singleton obiektu tylko raz na całe życie wniosku. Zobacz Apple Docs: GCD dispatch_once. Ja też jeszcze dodać allocWithZone: override trochę od starego singleton przykład Apple, i dodał prywatny Init nazwie singletonInit aby zapobiec przypadkowemu zwanych wiele razy:

//Hidden/Private initialization 
-(void)singletonInit 
{ 
    //your init code goes here 
} 

static HSCloudManager * sharedInstance = nil; 

+ (HSCloudManager *) sharedManager {         
    static dispatch_once_t dispatchOncePredicate = 0;     
    dispatch_once(&dispatchOncePredicate, ^{       
     sharedInstance = [[super allocWithZone:NULL] init];   
     [sharedInstance singletonInit];//Only place you should call singletonInit 
    });                 
    return sharedInstance;              
} 

+ (id) allocWithZone:(NSZone *)zone { 
    //If coder misunderstands this is a singleton, behave properly with 
    // ref count +1 on alloc anyway, and still return singleton! 
    return [[HSCloudManager sharedManager] retain]; 
} 

HSCloudManager podklasy NSObject i nie zastępują init pozostawiając jedynie domyślne implementacja w NSObject, która zgodnie z dokumentacją Apple zwraca tylko siebie. Oznacza to, że [[HSCloudManager alloc] init] jest taki sam jak [[[HSCloud Manager sharedManager] retain] self], dzięki czemu jest bezpieczny zarówno dla zagmatwanych użytkowników, jak i aplikacji wielowątkowych, jako leniwy singleton.

Jeśli chodzi o twoje obawy dotyczące podklasowania twojego singletonu przez użytkownika, powiedziałbym po prostu komentarz/udokumentuj to wyraźnie. Każdy na ślepo podklasy bez czytania na klasie jest prosząc o za ból!

EDIT # 3: Dla kompatybilność ARC, po prostu usunąć część zatrzymywania z allocWithZone: ręcznym, ale zachować nadpisanie.

+0

@yAaak, wydaje się dość sprawiedliwy , ale czuję się jak w nieskończonej pętli i muszę to przetrawić;) Jak mogę zapewnić, że proces tworzenia takiego singletona przez prywatną metodę jest bezpieczny dla wątków? Inny problem: jeśli podążę za pomysłem Apple 'allocWithZone', zostanie wywołany domyślny' init' NSObject (czy po prostu zwraca 'self' lub robi coś innego?) ... i wtedy znowu użytkownik próbuje utworzyć instancję za pomocą' alloc' i ' init' czy zmienia coś o moich właściwościach/inicjalizacji ivars i NSObject pobierającym 'init' ponownie? – matm

+0

YAak, masz rację: rozsądne podejście, które sugerujesz, na razie wystarcza. Będę grał z singletonami nieco bardziej przy użyciu twojego kodu :) Myślę, że twoja odpowiedź jest dość obszerna i bierze pod uwagę kontrpropozycję Catfish_man, więc akceptuję to. Dzięki jeszcze raz! – matm

+0

Proponuję dispatch_once() zamiast OSAtomic *. Mniej wybredny, równie szybki lub szybszy. –

2

. Będzie tak samo w klasie singleton, jak w zwykłej klasie. Może chcesz zastąpić allocWithZone: (która jest wywoływana przez alloc), aby uniknąć tworzenia więcej niż jednej instancji klasy.

3

Szczerze? Cała moda pisania singletonowych klas kuloodpornych wydaje mi się dość przesadzona. Jeśli poważnie się o to martwisz, po prostu wstawiaj tam (sharedInstance == zero) zanim przypiszesz go po raz pierwszy. W ten sposób zawiesi się, jeśli ktoś użyje go źle, natychmiast informując go, że jest idiotą.

+2

Częściowo zgadzam się, ale inna połowa mnie mówi: po co iść na kompromis? Jednak pójdę po to, co mogę osiągnąć plus jasne komentarze w nagłówku/dokumentach. – matm

-1

Spróbuj

+(id)allocWithZone:(struct _NSZone *)zone{ 
    static dispatch_once_t onceToken_alloc; 
    static id __alloc_instance; 
    dispatch_once(&onceToken_alloc, ^{ 
     __alloc_instance = [super allocWithZone:zone]; 
    }); 
    return __alloc_instance; 
} 

-(id)init{ 
    static id __instance; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     __instance = [super init]; 
    }); 
    return __instance; 
} 
+0

Przydziela nową instancję zamiast zwracać instancję singleton. – ebi

+0

Nie, nie ma. – dotAramis

+0

Wypróbowałem to i masz rację, ale singleton powinien implementować klasową metodę dostępu, zamiast wymagać alokacji wywołań konsumenckich, aby uzyskać instancję. – ebi

0

Aby init/nowe metody jest niedostępna dla osób dzwoniących z twojej klasy singleton można użyć NS_UNAVAILABLE makro w pliku nagłówka:

- (id)init NS_UNAVAILABLE; 
+ (id)new NS_UNAVAILABLE; 

+ (instancetype)sharedInstance; 
Powiązane problemy