web-dev-qa-db-fra.com

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

Si vous pouvez cibler iOS 4.0 ou supérieur

En utilisant 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;
}
339
Ryan

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

214
Dave DeLong

type d'instance

instancetype n'est qu'une des nombreuses extensions de langage de Objective-C, d'autres étant ajoutées à chaque nouvelle version.

Sache-le, aime-le.

Et prenez-le comme exemple de la manière dont l'attention portée aux détails de bas niveau peut vous donner un aperçu de nouveaux moyens puissants 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;
}
36
Zelko

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
33
Sergey Petruk

Vous pouvez éviter que la classe soit allouée en écrasant 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;
}
6
i-developer

Dave a raison, c'est très bien. Vous souhaiterez peut-être consulter documentation d'Apple sur la création d'un singleton pour obtenir des conseils sur la mise en œuvre de certaines des autres méthodes afin de garantir la création d'une seule si les classes choisissent de ne PAS utiliser la méthode sharedFoo.

5
Christian

Si vous voulez vous assurer que [[MyClass alloc] init] renvoie le même objet que sharedInstance (ce qui n'est pas nécessaire à mon avis, mais certains le veulent), vous pouvez le faire très facilement et en toute sécurité à l'aide d'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 de [MyClass sharedInstance] de renvoyer le même objet. [MyClass sharedInstance] serait simplement un peu plus efficace. Comment ça marche: [MyClass sharedInstance] appellera une fois [[MyClass alloc] init]. Un autre code pourrait aussi l'appeler, n'importe quel nombre de fois. Le premier appelant à init fera l'initialisation "normale" et stockera l'objet singleton à distance dans la méthode init. Tous les appels ultérieurs à init ignoreront complètement l'allocation retournée et renverront la même instance partagée. le résultat de alloc sera désalloué.

La méthode + sharedInstance fonctionnera comme toujours. 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.

4
gnasher729

Vous demandez si c'est le "meilleur moyen de créer singleton".

Quelques réflexions:

  1. Premièrement, oui, il s'agit d'une solution thread-safe. Ce modèle dispatch_once est le moyen moderne et sûr 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 le faire. Il faut cependant reconnaître que les instancetype et [[self alloc] init] sont potentiellement trompeurs s’ils sont utilisés conjointement avec des singletons.

    Le bénéfice de instancetype est qu’il s’agit d’une manière non équivoque de déclarer que la classe peut être sous-classée sans recourir à un type de id, comme nous l’avions déjà fait dans le passé.

    Mais le static de cette méthode présente des problèmes de sous-classification. Et si ImageCache et BlobCache singletons étaient tous les deux des sous-classes d'une super classe Cache sans implémenter leur propre méthode sharedCache?

    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 méthode sharedInstance (ou ce que vous appelez pour votre classe particulière).

    En bout de ligne, votre sharedInstanceressemble original supportera les sous-classes, mais ce ne sera pas le cas. Si vous avez l'intention de prendre en charge les sous-classes, incluez au minimum 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 que ce soit une propriété, pas une méthode de classe, par exemple:

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

    Vous pouvez ensuite écrire un getter pour cette propriété (l’implémentation utiliserait le modèle dispatch_once 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
    

    Notez qu'il n'y a pas de (), car nous l'avons implémenté en tant que propriété. À partir de Swift 3, voici comment accéder aux singletons. La définir comme une propriété facilite donc cette interopérabilité.

    En passant, si vous regardez comment Apple définit leurs singletons, c'est le modèle qu'ils ont adopté, par exemple. leur singleton NSURLSession est défini comme suit:

    @property (class, readonly, strong) NSURLSession *sharedSession;
    
  4. Le nom du singleton est un autre critère d'interopérabilité très mineur Swift. Il est préférable d'intégrer le nom du type plutôt que sharedInstance. Par exemple, si la classe était Foo, vous pouvez définir la propriété singleton comme sharedFoo. Ou si la classe était DatabaseManager, vous pouvez appeler la propriété sharedManager. Ensuite, les utilisateurs Swift pourraient effectuer:

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

    Clairement, si vous voulez vraiment utiliser sharedInstance, vous pouvez toujours déclarer le nom Swift si vous voulez:

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

    Il est clair que lorsque nous écrivons du code Objective-C, nous ne devrions pas laisser l’interopérabilité Swift l’emporter sur d’autres considérations de conception. Néanmoins, si nous pouvons écrire du code prenant en charge 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/ne devraient pas (accidentellement) instancier leurs propres instances, le qualificatif unavailable de init et new est prudent.

2
Rob

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 singleton singletons dans objc/cacoa

0
Hancock_Xu
//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
Rohit Kashyap