2011-09-01 13 views
16

Ho letto un paio di risorse incredibili sul single in Obj-C:Come singleton Objective-C dovrebbe implementare il metodo init?

  1. SO domanda: What does your Objective-C singleton look like?
  2. Venerdì D & A: Care and Feeding of Singletons
  3. docs Apple: Creating a Singleton Instance

ma nessuno di queste risorse è stato indirizzato al concetto di metodo initesplicitamente e pur essendo un novizio ghiaccio a Obj-C Sono confuso come dovrei implementarlo.

Finora so che avere init privato non è possibile in Obj-C in quanto non offre veri metodi privati ​​... quindi è possibile che l'utente può chiamare [[MyClass alloc] init] invece di usare la mia [MyClass sharedInstance].

Quali sono le mie altre opzioni? Credo che dovrei anche gestire gli scenari di sottoclassi del mio singleton.

risposta

25

Bene, un modo semplice per lo init è di non scriverne uno per farlo chiamare l'implementazione NSObject predefinita (che restituisce solo self). Quindi, per la tua funzione sharedInstance, definisci e chiama una funzione privata che esegue un lavoro di tipo init quando installi il tuo singleton. (Ciò evita all'utente di reinizializzare accidentalmente il singleton.)

Tuttavia !!! Il problema principale è che alloc viene chiamato da un utente del tuo codice! Per questo, io personalmente raccomando percorso di Apple di preminente allocWithZone: ...

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

Questo significa che l'utente sarà ancora ottenere l'istanza Singleton, e possono utilizzare erroneamente come se assegnate, e in modo sicuro rilasciarlo una volta da questo l'allocazione personalizzata esegue un mantenimento sul singleton. (Nota: alloc chiama allocWithZone: e non è necessario sostituirlo separatamente.)

Spero che questo aiuti! Fatemi sapere se volete maggiori informazioni ~

EDIT: Espansione risposta a fornire l'esempio e ulteriori dettagli -

Prendendo la risposta di Catfish_Man in considerazione, è spesso non è importante creare un Singleton a prova di proiettile, e invece basta scrivere alcuni commenti sensibili nelle intestazioni/documentazione e inserire un assert.

Tuttavia, nel mio caso, volevo un single-lazy-load thread-safe, ovvero non viene assegnato fino a quando non deve essere utilizzato, anziché essere assegnato automaticamente all'avvio dell'app. Dopo aver imparato come farlo in modo sicuro, ho pensato che potrei anche andare fino in fondo con esso.

EDIT # 2: Ora utilizzo GCD dispatch_once(...) per un approccio thread-safe di allocazione di un oggetto Singleton una sola volta per la durata di un'applicazione. Vedi Apple Docs: GCD dispatch_once. Ho anche aggiungere ancora allocWithZone: po 'esclusione dal vecchio esempio Singleton di Apple, e ha aggiunto un init privata denominata singletonInit per evitare che accidentalmente chiamato più volte:

//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 sottoclassi NSObject, e non sovrascrive init lasciando solo il default implementazione in NSObject, che come da documentazione di Apple restituisce solo auto. Ciò significa che lo [[HSCloudManager alloc] init] è lo stesso di [[[HSCloud Manager sharedManager] retain] self], che lo rende sicuro sia per gli utenti confusi che per le applicazioni multi-thread come un singleton con caricamento lento.

Per quanto riguarda la sottomissione del sottotitolo dell'utente, direi semplicemente commentarlo/documentarlo chiaramente. Chiunque ciecamente sottoclasse senza leggere la lezione è chiedendo il per il dolore!

EDIT # 3: Per ARC compatibilità, è sufficiente rimuovere la porzione trattenere dal allocWithZone: override, ma mantenere l'override.

+0

@yAaak, sembra abbastanza giusto , ma mi sento come in loop infinito e ho bisogno di digerirlo;) Come faccio a garantire che il processo di creazione di tale singleton tramite metodo privato sia thread-safe? Altro problema: se seguo l'idea di 'allocWithZone' di Apple, verrà chiamato il valore predefinito' init' di NSObject (non è che restituire 'self' o sth else?) ... e di nuovo l'utente prova a creare un'istanza con' alloc' e' init' non cambia nulla riguardo le mie proprietà/inizializzazione di ivars e NSObject ottiene di nuovo 'init'? – matm

+0

yAak, hai ragione: approccio sano che stai suggerendo che sia sufficiente per ora. Giocherò con i singleton un po 'di più usando il tuo codice :) Penso che la tua risposta sia piuttosto ampia e tenga conto del contro-parere di Catfish_man, quindi lo accetto. Grazie ancora! – matm

+0

Suggerisco dispatch_once() piuttosto che OSAtomic *. Meno schizzinoso, altrettanto veloce o veloce. –

2

Il metodo init non deve essere interessato. Sarà lo stesso in una classe singleton come in una classe normale. È possibile che si desideri eseguire l'override di allocWithZone: (che viene chiamato da alloc) per evitare di creare più di un'istanza della classe.

3

Onestamente? L'intera moda di scrivere classi singleton a prova di proiettile mi sembra alquanto esagerata. Se sei seriamente preoccupato a riguardo, metti semplicemente enunciato (sharedInstance == nil) prima di assegnarlo la prima volta. In questo modo si bloccherà se qualcuno lo userà male, facendogli prontamente sapere che sono un idiota.

+2

Parzialmente d'accordo, ma l'altra metà di me dice: perché scendere a compromessi? Comunque andrò per quello che posso ottenere più commenti chiari in header/docs. – matm

-1

Prova questa

+(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

Assegna una nuova istanza anziché restituire l'istanza Singleton. – ebi

+0

No, non è così. – dotAramis

+0

Provato e hai ragione, ma un singleton dovrebbe implementare un metodo di classe per l'accesso piuttosto che richiedere l'allocazione di chiamata del consumatore per ottenere l'istanza. – ebi

0

Per rendere l'init/nuovi metodi disponibili per i chiamanti della vostra classe Singleton è possibile utilizzare la macro NS_UNAVAILABLE nel file di intestazione:

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

+ (instancetype)sharedInstance; 
Problemi correlati