2015-06-12 2 views
5

So che ci sono diversi thread su questo, ma nessuno risponde alle mie domande.Singleton in iOS Obiettivo C non impedisce più di un'istanza

Ho implementato la mia classe Singleton come questo (essendo a conoscenza della controversia su single):

+ (MyClass*) sharedInstance { 
    static MyClass *_sharedInstance = nil; 
    static dispatch_once_t oncePredicate; 
    dispatch_once(&oncePredicate, ^{ 
     _sharedInstance = [[MyClass alloc] init]; 
    }); 
    return _sharedInstance; 
} 

- (instancetype)init{ 
    self = [super init]; 
    if (self) { 
     //setup code 
    } 
    return self; 
} 

ho provato un'istanza di un oggetto diverso e lo ha confrontato a quello restituito da sharedInstance con '==' ed erano davvero diversi.

Domande:

  1. non dovrebbero creando più di un oggetto della classe Singleton impossibile? Non è questo il punto? L'implementazione di Singleton in Java lo impedisce.
  2. E se sì, come? Devo fare un metodo di installazione e chiamarlo invece di avere implementato init e farlo?
  3. Questa corretta implementazione?
+0

Non dovrebbe usare single in questo modo a tutti. – Sven

+2

@Sven Non è molto utile. Potresti elaborare? Perché non dovrei farlo in questo modo? Sembra essere lo standard. – VeryPoliteNerd

+0

Questo non è altro che lo stato globale mutabile. Non meglio delle variabili globali. Questo porta a un codice difficile da ragionare, difficile da riutilizzare e difficile da testare. Inoltre, ad un certo punto nel tempo probabilmente avrai bisogno di più di una singola istanza. Se hai solo bisogno di una singola istanza, creane solo una. Con la corretta Iniezione delle dipendenze questo è veramente facile da fare. – Sven

risposta

5

Non è possibile rendere il metodo init privato, come si farebbe in Java con il costruttore. Quindi niente ti impedisce di chiamare [[MyClass alloc] init] che effettivamente crea un oggetto diverso. Finché non lo fai, ma rispetta il metodo sharedInstance, la tua implementazione va bene.

cosa si potrebbe fare: hanno il metodo init sollevare un'eccezione (ad esempio con [self doesNotRecognizeSelector:@_cmd]) ed eseguire l'inizializzazione in un metodo diverso (ad esempio privateInit) che non è esposta nel file di intestazione.

+0

È possibile salvare una variabile che dice se 'init' è già stato chiamato o meno, tuttavia quando gli utenti tentano di eludere l'interfaccia pubblica, non c'è davvero alcun robusto modo per prevenirli. – Sulthan

+0

Non è che io debba assolutamente impedirlo. Ho solo pensato che fosse una parte importante del concetto con un singleton. – VeryPoliteNerd

3

Per impedire la creazione di più oggetti di una singola classe, è necessario eseguire le seguenti operazioni. ti va bene creato un oggetto singleton. ma mentre si chiama init, copy, mutable copy, è necessario gestirli in questo modo.

- (instancetype)init{ 

    if (!_sharedInstance) { 
     _sharedInstance = [MyClass sharedInstance]; 
    } 
    return _sharedInstance; 
} 




- (id)copy{ 

     if (!_sharedInstance) { 
      _sharedInstance = [MyClass sharedInstance]; 
     } 
     return _sharedInstance; 
    } 

le stesse cose per copia mutabile pure. quindi questa implementazione si assicura che una volta che un'istanza è disponibile in tutto ..

Possa questo aiuto.

+0

@verypolitenerd, fammi sapere se ancora non ottieni chiarimenti .. –

+0

Nota che questo non sembra sicuro. E il metodo di copia potrebbe solo tornare autonomo - deve esserci un'istanza attorno alla quale è chiamato. – Eiko

-1

questo funziona per me:

static DataModel *singleInstance; 

+ (DataModel*)getInstance{ 
    if (singleInstance == nil) { 
     singleInstance = [[super alloc] init]; 
    } 
    return singleInstance; 
} 

si può chiamare con

_model = [DataModel getInstance]; 
+3

Completamente rotto quando si utilizzano più thread. Il dispatch_once è lì per un motivo. – gnasher729

12

La tua osservazione è corretta, molti dei modelli "singleton" che vedete in Objective-C non sono single a tutto tranne un modello di "istanza condivisa" in cui è possibile creare altre istanze.

Nei vecchi giorni MRC, Apple aveva un codice di esempio che mostrava come implementare un vero singleton.

Il codice che hai è il modello consigliato per ARC e single thread-safe, non vi resta che metterlo nel metodo init:

- (instancetype) init 
{ 
    static MyClass *initedObject; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     initedObject = [super init]; 
    }); 
    return initedObject; 
} 

Questo codice farà in modo che ci sia sempre e solo un esempio di MyClass indipendentemente dal numero di chiamate effettuate da [MyClass new] o da [[MyClass alloc] init].

Questo è tutto ciò che ha bisogno di da fare, ma si può andare oltre. In primo luogo, se si desidera avere un metodo di classe per restituire la Singleton è semplicemente:

+ (instancetype) singletonInstance 
{ 
    return [self new]; 
} 

Questo metodo finisce per chiamare init che restituisce il Singleton, creando se necessario.

If MyClass attrezzi NSCopying allora avete anche bisogno di implementare copyWithZone: - che è il metodo che copy chiamate. Come hai un Singleton questo è davvero semplice:

- (instancetype) copyWithZone:(NSZone *)zone 
{ 
    return self; 
} 

Infine in Objective-C le operazioni di assegnazione di una nuova istanza dell'oggetto e l'inizializzazione si sono distinti. Lo schema precedente assicura che sia inizializzata e utilizzata una sola istanza di MyClass, tuttavia per ogni chiamata a new o alloc viene allocata un'altra istanza, quindi scartata prontamente da init e eliminata da ARC. Questo è un po 'dispendioso!

Questo è facilmente indirizzata implementando allocWithZone: (come copy sopra questo è il metodo alloc finisce di chiamata) seguendo lo stesso schema per init:

+ (instancetype) allocWithZone:(NSZone *)zone 
{ 
    static MyClass *allocatedObject; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     allocatedObject = [super allocWithZone:zone]; 
    }); 
    return allocatedObject; 
} 

La prima volta un'istanza si crea quindi allocWithZone: sarà allocarlo e quindi init inizializzarlo, tutte le chiamate successive restituiranno l'oggetto già esistente. Nessuna allocazione non necessaria scartata.

Questo è tutto, un vero singleton, e non più difficile dei finti singoletti che sono così comuni.

HTH

+0

Non consiglierei questa implementazione, poiché la parola chiave "nuovo" significa qualcosa per gli sviluppatori, si prevede che la nuova istanza sia restituita da "nuovo" ma non singleton. – Codebear

+0

@Codebear - Quanto sopra è essenzialmente il vecchio design MRC di Apple da * Cocoa Fundamentals *. Il punto è che esiste sempre e solo un oggetto indipendentemente da quanti sono richiesti, è un vero singleton. Se ciò non si adatta, si utilizza il modello * shared instance * (spesso chiamato singleton) dove un metodo factory fornisce l'istanza condivisa, ma 'new' * et al * creano ancora nuovi oggetti distinti. Entrambi sono modelli validi, si sceglie quello appropriato per le circostanze, e in questo caso il vero singleton era ciò a cui la Domanda cercava di aiutare. – CRD

0

@VeryPoliteNerd solo segnare i init e new metodi come non disponibili sul .h:

- (instancetype)init __attribute__((unavailable("Use +[MyClass sharedInstance] instead"))); 

+ (instancetype)new __attribute__((unavailable("Use +[MyClass sharedInstance] instead"))); 

questo farà sì che il compilatore a lamentarsi se un chiamante tenta di creare un'istanza manualmente questo oggetti

+0

In questo caso non sono in grado di utilizzare init su self. Quindi come potrei allocare e avviare il mio oggetto. – Javeed

+0

@avviato nel tuo '.m' crei un' initInternal' o qualsiasi cosa tu voglia chiamarlo e usalo quando crei il singleton – esttorhe

0

La chiamata alloc/init per ottenere una seconda istanza di una classe Singleton è considerata un errore di programmazione palese. Per evitare questo tipo di errore di programmazione, non si scrive codice complicato per impedirlo, si eseguono revisioni del codice e si dichiara che tutti cercano di farlo, come si farebbe con qualsiasi errore di programmazione.

2

Con l'obiettivo-c, è possibile impedire alla classe singleton di creare più di un oggetto. È possibile impedire alloc e init call con la classe singleton.

#import <Foundation/Foundation.h> 

@interface SingletonClass : NSObject 

+ (id) sharedInstance; 
- (void) someMethodCall; 
- (instancetype) init __attribute__((unavailable("Use +[SingletonClass sharedInstance] instead"))); 
+ (instancetype) new __attribute__ ((unavailable("Use +[SingletonClass sharedInstance] instead"))); 

@end 


#import "SingletonClass.h" 

@implementation SingletonClass 

+ (id) sharedInstance{ 
    static SingletonClass * sharedObject = nil; 
    static dispatch_once_t onceToken; 

    dispatch_once(&onceToken, ^{ 
     sharedObject = [[self alloc] initPrivate]; 
    }); 

    return sharedObject; 
} 

- (instancetype)init { 
    @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"You can't override the init call in class %@", NSStringFromClass([self class])] userInfo:nil]; 
} 

- (instancetype)initPrivate { 
    if (self = [super init]) { 

    } 
    return self; 
} 


- (void) someMethodCall{ 
    NSLog(@"Method Call"); 
} 

@end 

1 # Se si cercherà di chiamare i metodi init o nuove su SingletonClass, allora questi metodi non sarebbero disponibili a chiamare.

2 # Se si come commento di seguito indicate metodi nel file di intestazione e tenta di chiamare il metodo init su SingletonClass quindi applicazione sarà schiantato con la ragione "Non si può ignorare la chiamata init in classe SingletonClass".

- (instancetype) init __attribute__((unavailable("Use +[SingletonClass sharedInstance] instead"))); 
    + (instancetype) new __attribute__ ((unavailable("Use +[SingletonClass sharedInstance] instead"))); 

basta usare questo codice per creare singolo oggetto per Singleton Pattern e prevenire chiamata init alloc per pattern Singleton da altre classi. Ho provato questo codice con xCode 7.0+ e funziona correttamente.

0

questo funziona per me:

static AudioRecordingGraph * __strong sharedInstance; 

+(instancetype)sharedInstance { 

    @synchronized(self) { 

     if(!sharedInstance) { 

      sharedInstance = [AudioRecordingGraph new]; 
     } 
     return sharedInstance; 
    } 
}