Crea singleton usando dispatch_once di GCD in Objective-C


341

Se puoi scegliere come target iOS 4.0 o versioni successive

Usando GCD, è il modo migliore per creare singleton in Objective-C (thread-safe)?

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

2
C'è un modo per impedire agli utenti della classe di chiamare alloc / copy?
Nicolas Miari,

3
dispatch_once_t e dispatch_once sembrano essere stati introdotti in 4.0, non 4.1 (vedi: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn,

1
Questo metodo diventa problematico se init richiede l'uso dell'oggetto singleton. Il codice di Matt Gallagher ha funzionato per me in più di alcune occasioni. cocoawithlove.com/2008/11/…
greg

1
So che non ha importanza in questo esempio; ma perché le persone non usano più "nuovo"? dispatch_once (& once, ^ {sharedInstance = [self new];} sembra proprio quel bit più ordinato. Equivale a alloc + init.
Chris Hatton,

3
Assicurati di iniziare a utilizzare il tipo restituito instancetype. Il completamento del codice è molto meglio quando si utilizza quello invece di id.
Mr Rogers,

Risposte:


215

Questo è un modo perfettamente accettabile e sicuro per creare un'istanza della tua classe. Tecnicamente potrebbe non essere un "singleton" (in quanto può esistere solo 1 di questi oggetti), ma fintanto che usi il [Foo sharedFoo]metodo solo per accedere all'oggetto, questo è abbastanza buono.


4
Come lo rilascia però?
Samvermette,

65
@samvermette no. il punto di un singleton è che esisterà sempre. pertanto, non lo si rilascia e la memoria viene recuperata con l'uscita del processo.
Dave DeLong,

6
@Dave DeLong: Secondo me lo scopo di avere singleton non è una certezza della sua immortalità, ma la certezza che abbiamo un esempio. E se quel singleton decrementasse un semaforo? Non puoi semplicemente dire arbitrariamente che esisterà sempre.
Jacekmigacz,

4
@hooleyhoop Sì, nella sua documentazione . "Se chiamata contemporaneamente da più thread, questa funzione attende in modo sincrono fino al completamento del blocco."
Kevin,

3
@ WalterMartinVargas-Pena il forte riferimento è tenuto dalla variabile statica
Dave DeLong

36

instanceType

instancetypeè solo una delle molte estensioni linguistiche a Objective-C, con altre aggiunte ad ogni nuova versione.

Conoscilo, lo adoro.

E prendilo come un esempio di come prestare attenzione ai dettagli di basso livello può darti informazioni su nuovi potenti modi di trasformare Objective-C.

Fare riferimento qui: instancetype


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

4
fantastico suggerimento, grazie! instancetype è una parola chiave contestuale che può essere utilizzata come tipo di risultato per segnalare che un metodo restituisce un tipo di risultato correlato. ... Con instancetype, il compilatore inferirà correttamente il tipo.
Fattie,

1
Non mi è chiaro cosa significano qui i due frammenti, sono equivalenti? Uno è preferibile all'altro? Sarebbe bello se l'autore potesse aggiungere qualche spiegazione per questo.
galactica

33

MySingleton.h

@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

MySingleton.m

@implementation MySingleton

+(instancetype)sharedInstance {
    static dispatch_once_t pred;
    static id shared = nil;
    dispatch_once(&pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

-(instancetype)initUniqueInstance {
    return [super init];
}

@end

In che modo l'init non è disponibile? Non è almeno disponibile per uno init?
Miele

2
Singleton dovrebbe avere un solo punto di accesso. E questo punto è condivisoInstance. Se abbiamo il metodo init nel file * .h di quello che puoi creare un'altra istanza singleton. Ciò contraddice la definizione di singleton.
Sergey Petruk,

1
@ asma22 __attribute __ ((non disponibile ()) non è disponibile per l'uso di questi metodi. Se un altro programmatore desidera utilizzare il metodo contrassegnato come non disponibile, ottiene un errore
Sergey Petruk

1
Ho completamente capito e sono felice di aver imparato qualcosa di nuovo, niente di sbagliato nella tua risposta, potrebbe essere un po 'confuso per i neofiti ...
Miele

1
Questo funziona solo per MySingleton, ad esempio, MySingleton.mio chiamo[super alloc]
Sergey Petruk,

6

È possibile evitare che la classe venga allocata sovrascrivendo il metodo alloc.

@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}

1
Questo risponde alla mia domanda nei commenti sopra. Non che io sia così tanto per la programmazione difensiva, ma ...
Nicolas Miari,

5

Dave ha ragione, va benissimo. Potresti voler consultare i documenti di Apple sulla creazione di un singleton per suggerimenti sull'implementazione di alcuni degli altri metodi per garantire che solo uno possa mai essere creato se le classi scelgono di NON utilizzare il metodo sharedFoo.


8
eh ... questo non è il miglior esempio di creazione di un singleton. Non è necessario ignorare i metodi di gestione della memoria.
Dave DeLong,

19
Questo non è completamente valido usando ARC.
logancautrell,

Il documento citato da allora è stato ritirato. Inoltre, le risposte che sono esclusivamente collegamenti a contenuti esterni sono generalmente risposte SO scarse. Con un estratto minimo porzioni pertinenti all'interno della tua risposta. Non preoccuparti qui a meno che tu non voglia salvare il vecchio modo per i posteri.
toolbear,

4

Se vuoi assicurarti che [[MyClass alloc] init] restituisca lo stesso oggetto di sharedInstance (non necessario secondo me, ma alcuni lo vogliono), che può essere fatto molto facilmente e in sicurezza usando un secondo dispatch_once:

- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here. 
        sharedInstance = self;
    });

    return sharedInstance;
}

Ciò consente a qualsiasi combinazione di [[MyClass alloc] init] e [MyClass sharedInstance] di restituire lo stesso oggetto; [MyClass sharedInstance] sarebbe solo un po 'più efficiente. Come funziona: [MyClass sharedInstance] chiamerà [[MyClass alloc] init] una volta. Anche un altro codice potrebbe chiamarlo, un numero qualsiasi di volte. Il primo chiamante a init eseguirà l'inizializzazione "normale" e memorizzerà l'oggetto singleton nel metodo init. Qualsiasi chiamata successiva a init ignorerà completamente quale allocazione ha restituito e restituirà la stessa sharedInstance; il risultato dell'allocazione verrà deallocato.

Il metodo + sharedInstance funzionerà come sempre. Se non è il primo chiamante a chiamare [[MyClass alloc] init], il risultato di init non è il risultato della chiamata di allocazione, ma va bene.


2

Ti chiedi se questo è il "modo migliore per creare singleton".

Alcuni pensieri:

  1. Innanzitutto, sì, questa è una soluzione thread-safe. Questo dispatch_oncemodello è il modo moderno e sicuro per generare singleton in Objective-C. Nessuna preoccupazione lì.

  2. Hai chiesto, tuttavia, se questo è il modo "migliore" per farlo. Si dovrebbe riconoscere, tuttavia, che instancetypee [[self alloc] init]è potenzialmente fuorviante se utilizzato in combinazione con i singoli.

    Il vantaggio instancetypeè che è un modo inequivocabile di dichiarare che la classe può essere sottoclassata senza ricorrere a un tipo di id, come abbiamo dovuto fare in passato.

    Ma staticin questo metodo si presentano sfide di sottoclasse. E se ImageCachee i BlobCachesingoli fossero entrambe le sottoclassi di una Cachesuperclasse senza implementare il proprio sharedCachemetodo?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    Perché questo funzioni, dovresti assicurarti che le sottoclassi implementino il loro sharedInstancemetodo (o come lo chiami per la tua classe particolare).

    In conclusione, l'originale sharedInstance sembra supportare le sottoclassi, ma non lo farà. Se si intende supportare la sottoclasse, almeno includere la documentazione che avverte i futuri sviluppatori che devono ignorare questo metodo.

  3. Per una migliore interoperabilità con Swift, probabilmente si desidera definire questo come una proprietà, non un metodo di classe, ad esempio:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    Quindi puoi andare avanti e scrivere un getter per questa proprietà (l'implementazione utilizzerà il dispatch_oncemodello che hai suggerito):

    + (Foo *)sharedFoo { ... }

    Il vantaggio è che se un utente Swift lo usa, farebbe qualcosa del tipo:

    let foo = Foo.shared

    Nota, no (), perché l'abbiamo implementato come proprietà. A partire da Swift 3, in questo modo si accede generalmente ai singoli. Quindi definirlo come proprietà aiuta a facilitare tale interoperabilità.

    A parte questo, se osservi come Apple sta definendo i loro singoli, questo è lo schema che hanno adottato, ad esempio il loro NSURLSessionsingleton è definito come segue:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Un'altra considerazione di interoperabilità di Swift molto minore era il nome del singleton. È meglio se puoi incorporare il nome del tipo, piuttosto che sharedInstance. Ad esempio, se la classe fosse Foo, è possibile definire la proprietà singleton come sharedFoo. O se la lezione fosse DatabaseManager, potresti chiamare la proprietà sharedManager. Quindi gli utenti Swift potrebbero fare:

    let foo = Foo.shared
    let manager = DatabaseManager.shared

    Chiaramente, se vuoi davvero usarlo sharedInstance, puoi sempre dichiarare il nome Swift se vuoi:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    Chiaramente, quando scriviamo il codice Objective-C, non dovremmo lasciare che l'interoperabilità di Swift superi altre considerazioni di progettazione, ma comunque, se possiamo scrivere un codice che supporta con grazia entrambi i linguaggi, è preferibile.

  5. Concordo con gli altri che sottolineano che se si desidera che questo sia un vero singleton in cui gli sviluppatori non possono / non debbano (involontariamente) istanziare le proprie istanze, il unavailablequalificatore è attivo inited newè prudente.


0

Per creare un thread singolo sicuro puoi fare così:

@interface SomeManager : NSObject
+ (id)sharedManager;
@end

/* thread safe */
@implementation SomeManager

static id sharedManager = nil;

+ (void)initialize {
    if (self == [SomeManager class]) {
        sharedManager = [[self alloc] init];
    }
}

+ (id)sharedManager {
    return sharedManager;
}
@end

e questo blog spiega singleton singletons in objc / cacao


stai collegando a un articolo molto vecchio mentre OP richiede caratteristiche sulla più moderna implementazione.
vikingosegundo,

1
La domanda riguarda un'implementazione specifica. Hai appena pubblicato un'altra implementazione. Ecco perché non provi nemmeno a rispondere alla domanda.
vikingosegundo,

1
@vikingosegundo Il richiedente chiede al meteo GCD è il modo migliore per creare un singleton thread sicuro, la mia risposta dà un'altra scelta. Qualcosa non va?
Hancock_Xu,

il richiedente chiede se una determinata implementazione è thread-safe. non sta chiedendo opzioni.
vikingosegundo,

0
//Create Singleton  
  +( instancetype )defaultDBManager
    {

        static dispatch_once_t onceToken = 0;
        __strong static id _sharedObject = nil;

        dispatch_once(&onceToken, ^{
            _sharedObject = [[self alloc] init];
        });

        return _sharedObject;
    }


//In it method
-(instancetype)init
{
    self = [super init];
  if(self)
     {
   //Do your custom initialization
     }
     return self;
}

0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.