Créez un singleton à l'aide de dispatch_once de GCD dans Objective-C

341

Si vous pouvez cibler iOS 4.0 ou supérieur

À l'aide de GCD, est-ce le meilleur moyen de créer un singleton en Objective-C (thread safe)?

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
Ryan
la source
2
Existe-t-il un moyen d'empêcher les utilisateurs de la classe d'appeler alloc / copy?
Nicolas Miari
3
dispatch_once_t et dispatch_once semblent avoir été introduits dans 4.0, pas 4.1 (voir: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn
1
Cette méthode devient problématique si init nécessite l'utilisation de l'objet singleton. Le code de Matt Gallagher a fonctionné pour moi à plusieurs reprises. cocoawithlove.com/2008/11/…
greg
1
Je connais son importance dans cet exemple; mais pourquoi les gens n'utilisent-ils pas plus «nouveau». dispatch_once (& once, ^ {sharedInstance = [self new];} a juste l'air plus net. C'est équivalent à alloc + init.
Chris Hatton
3
Assurez-vous de commencer à utiliser le type de retour instancetype. La complétion de code est bien meilleure lorsque vous l'utilisez au lieu de id.
M. Rogers

Réponses:

215

C'est un moyen parfaitement acceptable et thread-safe de créer une instance de votre classe. Il peut ne pas être techniquement un "singleton" (en ce sens qu'il ne peut y avoir qu'un seul de ces objets), mais tant que vous n'utilisez la [Foo sharedFoo]méthode que pour accéder à l'objet, c'est suffisant.

Dave DeLong
la source
4
Comment le libérez-vous cependant?
samvermette
65
@samvermette non. le point d'un singleton est qu'il existera toujours. par conséquent, vous ne le libérez pas et la mémoire est récupérée à la fin du processus.
Dave DeLong
6
@Dave DeLong: À mon avis, le but d'avoir un singleton n'est pas une certitude de son immortalité, mais la certitude que nous avons une instance. Et si ce singleton décrémentait un sémaphore? Vous ne pouvez pas dire de façon arbitraire qu'elle existera toujours.
jacekmigacz
4
@hooleyhoop Oui, dans sa documentation . "Si elle est appelée simultanément à partir de plusieurs threads, cette fonction attend de manière synchrone jusqu'à la fin du bloc."
Kevin
3
@ WalterMartinVargas-Pena la référence forte est détenue par la variable statique
Dave DeLong
36

type d'instance

instancetypen'est qu'une des nombreuses extensions linguistiques de Objective-C, avec plus étant ajouté avec chaque nouvelle version.

Sachez-le, aimez-le.

Et prenez-le comme un exemple de la façon dont prêter attention aux détails de bas niveau peut vous donner un aperçu de nouvelles façons puissantes de transformer Objective-C.

Référez-vous ici: 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;
}
Zelko
la source
4
astuce incroyable, merci! instancetype est un mot-clé contextuel qui peut être utilisé comme type de résultat pour signaler qu'une méthode renvoie un type de résultat associé. ... Avec instancetype, le compilateur déduira correctement le type.
Fattie
1
Je ne comprends pas bien ce que les deux extraits signifient ici, sont-ils équivalents? L'un est préférable à l'autre? Ce serait bien si l'auteur pouvait ajouter quelques explications à ce sujet.
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
Sergey Petruk
la source
Comment l'init n'est-il pas disponible? N'est-il pas au moins disponible pour un init?
Honey
2
Singleton ne devrait avoir qu'un seul point d'accès. Et ce point est partagéInstance. Si nous avons la méthode init dans le fichier * .h, vous pouvez créer une autre instance singleton. Cela contredit la définition d'un singleton.
Sergey Petruk
1
@ asma22 __attribute __ ((indisponible ()) ne permet pas d'utiliser ces méthodes. Si un autre programmeur souhaite utiliser une méthode marquée comme indisponible, il obtient une erreur
Sergey Petruk
1
Je comprends parfaitement et je suis heureux d'avoir appris quelque chose de nouveau, rien de mal à votre réponse, peut être un peu déroutant pour les débutants ...
Honey
1
Cela ne fonctionne que pour MySingleton, par exemple dans MySingleton.mj'appelle[super alloc]
Sergey Petruk
6

Vous pouvez éviter que la classe soit allouée en remplaçant la méthode 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;
}
i-développeur
la source
1
Cela répond à ma question dans les commentaires ci-dessus. Non pas que je sois tellement pour la programmation défensive, mais ...
Nicolas Miari
5

Dave a raison, c'est très bien. Vous voudrez peut-être consulter les documents d'Apple sur la création d'un singleton pour obtenir des conseils sur la mise en œuvre de certaines des autres méthodes pour vous assurer qu'une seule peut jamais être créée si les classes choisissent de NE PAS utiliser la méthode sharedFoo.

Christian
la source
8
eh ... ce n'est pas le meilleur exemple de création d'un singleton. Il n'est pas nécessaire de remplacer les méthodes de gestion de la mémoire.
Dave DeLong
19
Ceci est complètement invalide en utilisant ARC.
logancautrell
Le document cité a depuis été retiré. De plus, les réponses qui sont uniquement des liens vers du contenu externe sont généralement de mauvaises réponses SO. Au moins un extrait des parties pertinentes de votre réponse. Ne vous embêtez pas ici à moins que vous ne vouliez que l'ancienne façon soit sauvegardée pour la postérité.
toolbear
4

Si vous voulez vous assurer que [[MyClass alloc] init] renvoie le même objet que sharedInstance (pas nécessaire à mon avis, mais certains le veulent), cela peut être fait très facilement et en toute sécurité en utilisant un second 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;
}

Cela permet à toute combinaison de [[MyClass alloc] init] et [MyClass sharedInstance] de renvoyer le même objet; [MyClass sharedInstance] serait juste un peu plus efficace. Fonctionnement: [MyClass sharedInstance] appellera une fois [[MyClass alloc] init]. Un autre code pourrait l'appeler aussi, un certain nombre de fois. Le premier appelant à init effectuera l'initialisation "normale" et stockera l'objet singleton dans la méthode init. Tout appel ultérieur à init ignorera complètement ce que alloc a renvoyé et renverra la même instance partagée; le résultat de l'allocation sera désaffecté.

La méthode + sharedInstance fonctionnera comme elle l'a toujours fait. Si ce n'est pas le premier appelant à appeler [[MyClass alloc] init], le résultat de init n'est pas le résultat de l'appel alloc, mais c'est OK.

gnasher729
la source
2

Vous demandez si c'est la "meilleure façon de créer un singleton".

Quelques réflexions:

  1. Tout d'abord, oui, c'est une solution thread-safe. Ce dispatch_oncemodèle est le moyen moderne et sans fil de générer des singletons dans Objective-C. Pas de soucis là-bas.

  2. Vous avez cependant demandé si c'était la "meilleure" façon de procéder. Il faut cependant reconnaître que le instancetypeet [[self alloc] init]est potentiellement trompeur lorsqu'il est utilisé conjointement avec des singletons.

    L'avantage de instancetypec'est que c'est une façon non ambiguë de déclarer que la classe peut être sous-classée sans avoir recours à un type de id, comme nous le faisions autrefois.

    Mais le staticdans cette méthode présente des défis de sous-classement. Que faire si ImageCacheet BlobCachesingletons étaient les deux sous - classes d'une Cachesuperclasse sans mettre en oeuvre leur propre sharedCacheméthode?

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

    Pour que cela fonctionne, vous devez vous assurer que les sous-classes implémentent leur propre sharedInstanceméthode (ou tout autre nom pour votre classe particulière).

    En bout de ligne, votre original sharedInstance semble prendre en charge les sous-classes, mais ce ne sera pas le cas. Si vous avez l'intention de prendre en charge le sous-classement, incluez à tout le moins une documentation qui avertit les futurs développeurs qu'ils doivent remplacer cette méthode.

  3. Pour une meilleure interopérabilité avec Swift, vous souhaiterez probablement définir cela comme une propriété, pas une méthode de classe, par exemple:

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

    Ensuite, vous pouvez continuer et écrire un getter pour cette propriété (l'implémentation utiliserait le dispatch_oncemodèle que vous avez suggéré):

    + (Foo *)sharedFoo { ... }

    L'avantage de ceci est que si un utilisateur Swift va l'utiliser, il ferait quelque chose comme:

    let foo = Foo.shared

    Remarque, il n'y en a pas (), car nous l'avons implémenté en tant que propriété. À partir de Swift 3, c'est ainsi que les singletons sont généralement accessibles. Le définir comme une propriété facilite donc cette interopérabilité.

    En passant, si vous regardez comment Apple définit ses singletons, voici le modèle qu'ils ont adopté, par exemple leur NSURLSessionsingleton est défini comme suit:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Une autre considération d'interopérabilité Swift très mineure était le nom du singleton. Il est préférable d'incorporer le nom du type plutôt que sharedInstance. Par exemple, si la classe l'était Foo, vous pourriez définir la propriété singleton comme sharedFoo. Ou si la classe l'était DatabaseManager, vous pourriez appeler la propriété sharedManager. Les utilisateurs de Swift pourraient alors faire:

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

    De toute évidence, si vous voulez vraiment l'utiliser sharedInstance, vous pouvez toujours déclarer le nom Swift si vous le souhaitez:

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

    De toute évidence, lors de l'écriture de code Objective-C, nous ne devons pas laisser l'interopérabilité Swift l'emporter sur d'autres considérations de conception, mais quand même, si nous pouvons écrire du code qui prend en charge gracieusement les deux langages, c'est préférable.

  5. Je suis d'accord avec d'autres qui soulignent que si vous voulez que ce soit un vrai singleton où les développeurs ne peuvent pas / ne devraient pas (accidentellement) instancier leurs propres instances, le unavailablequalificatif est activé initet newest prudent.

Rob
la source
0

Pour créer un singleton thread-safe, vous pouvez faire comme ceci:

@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

et ce blog explique très bien les singletons singleton en objc / cacao

Hancock_Xu
la source
vous créez un lien vers un article très ancien tandis qu'OP demande des caractéristiques sur la mise en œuvre la plus moderne.
vikingosegundo
1
La question concerne une implémentation spécifique. Vous venez de publier une autre implémentation. Là, vous n'essayez même pas de répondre à la question.
vikingosegundo
1
@vikingosegundo Le demandeur demande si le GCD est le meilleur moyen de créer un singleton Thread safe, ma réponse donne un autre choix.
Hancock_Xu
le demandeur demande si une certaine implémentation est thread-safe. il ne demande pas d'options.
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;
}
Rohit Kashyap
la source
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;
}
Nayab Muhammad
la source