web-dev-qa-db-fra.com

À quoi devrait ressembler mon singleton Objective-C?

Ma méthode d'accès singleton est généralement une variante de:

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

Que pourrais-je faire pour améliorer cela?

334
schwa

Une autre option consiste à utiliser la méthode +(void)initialize. De la documentation:

Le moteur d'exécution envoie initialize à chaque classe d'un programme exactement une fois, juste avant que la classe, ou toute classe qui en hérite, reçoive son premier message depuis le programme. (Ainsi, la méthode ne peut jamais être invoquée si la classe n'est pas utilisée.) Le moteur d'exécution envoie le message initialize aux classes d'une manière thread-safe. Les superclasses reçoivent ce message avant leurs sous-classes.

Donc, vous pouvez faire quelque chose qui ressemble à ceci:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}
207
Robbie Hanson
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[Source]

95
Ben Hoffstein

Selon mon autre réponse ci-dessous, je pense que vous devriez faire:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}
59
Colin Barrett

Puisque Kendall a posté un singleton threadsafe qui tente d’éviter le blocage des coûts, je pensais en lancer un aussi:

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

+ (className *) sharedInstance {                                                                    
  while (!sharedInstance) {                                                                          
    className *temp = [[self alloc] init];                                                                 
    if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
      [temp release];                                                                                   
    }                                                                                                    
  }                                                                                                        
  return sharedInstance;                                                                        
}

Ok, laissez-moi vous expliquer comment cela fonctionne:

  1. Cas rapide: En exécution normale, sharedInstance a déjà été défini. La boucle while n'est donc jamais exécutée et la fonction revient après avoir simplement testé l'existence de la variable.

  2. Cas lent: Si sharedInstance n'existe pas, une instance est allouée et copiée dans celle-ci à l'aide d'un Compare And Swap ('CAS');

  3. Cas concerné: Si deux threads tentent tous deux d'appeler sharedInstance simultanément ETsharedInstance n'existe pas à En même temps, ils initialiseront de nouvelles instances du singleton et tenteront de le placer en position CAS. Quel que soit le joueur qui gagne, les retours CAS immédiatement, celui qui perd, libère l'instance qu'il vient d'allouer et renvoie le (désormais défini) sharedInstance. L'unique OSAtomicCompareAndSwapPtrBarrier agit à la fois comme une barrière en écriture pour le thread de réglage et une barrière en lecture du thread de test.

58
Louis Gerbarg
 static MyClass * sharedInst = nil; 
 
 + (id) sharedInstance 
 {
 @synchronize (self) {
 si (sharedInst == nil) {
/* sharedInst configuré dans init */
 [[self alloc] init]; 
} 
} 
 return sharedInst; 
} 
 
 - (id) init 
 {
 if (sharedInst! = nil) {
 [ NSException levée: format NSInternalInconsistencyException 
: @ "[% @% @] Ne peut pas être appelé; utilisez + [% @% @] à la place"], 
 NSStringFromClass ([self class]), NSStringFromSelector ( _cmd), 
 NSStringFromClass ([self class]), 
 NSStringFromSelector (@selector (sharedInstance) "]; 
} sinon if (self = [super init]) {
 sharedInst = self; 
/* Quelle que soit la classe spécifique ici */
} 
 renvoyer sharedInst; 
} 
 
/* Celles-ci ne font probablement rien dans 
 Une application du gouvernement du Canada. singleton 
 en tant que singleton dans une application 
 non CG 
 */
 - (NSUInteger) holdCount 
 {
 return NSUIntegerMax ; 
} 
 
 - (vide à sens unique) relâcher 
 {
} 
 
 - (id) retenir 
 {
 return sharedInst; 
} 
 
 - (id) autorelease 
 {
 return sharedInst; 
} 
14
Michael Nickerson

Edit: Cette implémentation est obsolète avec ARC. Veuillez regarder Comment puis-je implémenter un singleton Objective-C compatible avec ARC? pour une implémentation correcte

Toutes les implémentations d'initialize que j'ai lues dans d'autres réponses partagent une erreur commune.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

La documentation Apple vous recommande de vérifier le type de classe dans votre bloc d'initialisation. Parce que les sous-classes appellent l'initialisation par défaut. Il existe un cas non évident où des sous-classes peuvent être créées indirectement par le biais de KVO. Car si vous ajoutez la ligne suivante dans une autre classe:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

Objective-C créera implicitement une sous-classe de MySingletonClass, ce qui entraînera un second déclenchement de +initialize.

Vous pouvez penser que vous devriez implicitement vérifier l'initialisation en double dans votre bloc init en tant que tel:

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

Mais vous vous tirerez une balle dans le pied; ou pire, donnez à un autre développeur la possibilité de se tirer une balle dans le pied.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL; DR, voici mon implémentation

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

(Remplacez ZAssert par notre propre macro d’affirmation; ou seulement par NSAssert.)

12
lorean

Une explication détaillée du code macro Singleton est disponible sur le blog Cocoa With Love

http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html .

10
Matthieu Cormier

J'ai une variante intéressante sur sharedInstance qui est thread-safe, mais ne se verrouille pas après l'initialisation. Je ne suis pas encore assez sûr de pouvoir modifier la réponse en haut comme demandé, mais je la présente pour une discussion plus approfondie:

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

Réponse courte: fabuleux.

Réponse longue: quelque chose comme ....

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

+ (id) instance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == NULL){
            instance = [[super allocWithZone:NULL] init];
        }
    });
    return instance;
}

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

- (id) copyWithZone:(NSZone *)paramZone {
    return self;
}

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

Assurez-vous de lire le dispatch/once.h pour comprendre ce qui se passe. Dans ce cas, les commentaires d'en-tête sont plus applicables que la documentation ou la page de manuel.

6
quellish

J'ai converti singleton dans une classe, afin que d'autres classes puissent hériter des propriétés de singleton.

Singleton.h:

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

Singleton.m:

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

+ (id) sharedInstance:(id*)inst {
    @synchronized(self)
    {
        if (*inst == nil)
            *inst = [[self alloc] init];
    }
    return *inst;
}

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
    @synchronized(self) {
        if (*inst == nil) {
            *inst = [super allocWithZone:zone];
            return *inst;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

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

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

Et voici un exemple de classe dans laquelle vous souhaitez devenir singleton.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

La seule limitation concernant la classe Singleton est qu'il s'agit d'une sous-classe NSObject. Mais la plupart du temps, j'utilise des singletons dans mon code, il s'agit en fait de sous-classes NSObject. Cette classe facilite donc vraiment ma vie et rend le code plus propre.

5
obscenum

Cela ne devrait-il pas être threadsafe et éviter le verrouillage coûteux après le premier appel?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}
2
Jompe

Cela fonctionne également dans un environnement non récupéré.

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

+(MySingleton*)sharedManager {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedMySingleton;
}


+(id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            sharedMySingleton = [super allocWithZone:zone];
            return sharedMySingleton;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}


-(void)dealloc {
    [super dealloc];
}

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


-(id)retain {
    return self;
}


-(unsigned)retainCount {
    return UINT_MAX;  //denotes an object that cannot be release
}


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


-(id)init {
    self = [super init];
    sharedMySingleton = self;

    //initialize here

    return self;
}

@end
2
lajos

Que diriez-vous

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

Vous évitez donc les coûts de synchronisation après l'initialisation?

2
Tony

Pour une discussion approfondie du motif singleton dans Objective-C, regardez ici:

tilisation du pattern Singleton dans Objective-C

2
Fred McCann

Voici une macro que j'ai mis ensemble:

http://github.com/cjhanson/Objective-C-Optimized-Singleton

Il est basé sur le travail ici de Matt Gallagher Mais changer l'implémentation pour utiliser méthode swizzling comme décrit ici par Dave MacLachlan de Google .

Je me félicite des commentaires/contributions.

2
CJ Hanson

KLSingleton est:

  1. Subclassible (au nième degré)
  2. Compatible ARC
  3. Coffre-fort avec alloc et init
  4. Chargé paresseusement
  5. Fil-safe
  6. Sans verrouillage (utilise + initialize, pas @synchronize)
  7. Sans macro
  8. Sans grésillement
  9. Facile

KLSingleton

1
kevinlawler

Je sais qu'il y a beaucoup de commentaires sur cette "question", mais je ne vois pas beaucoup de gens suggérer d'utiliser une macro pour définir le singleton. C'est un modèle si commun et une macro simplifie grandement le singleton.

Voici les macros que j'ai écrites à partir de plusieurs implémentations Objc que j'ai vues.

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

Exemple d'utilisation:

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

Pourquoi une macro d'interface alors qu'elle est presque vide? Cohérence du code entre les fichiers d’en-tête et de code; maintenabilité au cas où vous voudriez ajouter plus de méthodes automatiques ou la changer.

J'utilise la méthode d'initialisation pour créer le singleton tel qu'il est utilisé dans la réponse la plus populaire ici (au moment de l'écriture).

0
Nate

Avec les méthodes de classe Objective C, nous pouvons simplement éviter d'utiliser le motif singleton de la manière habituelle, à partir de:

[[Librarian sharedInstance] openLibrary]

à:

[Librarian openLibrary]

en encapsulant la classe dans une autre classe qui a juste Méthodes de classe , de cette façon, il n'y a aucune chance de créer accidentellement des instances en double, car nous ne créons pas exemple!

J'ai écrit un blog plus détaillé ici :)

0
chunkyguy

Pour étendre l'exemple de @ robbie-hanson ...

static MySingleton* sharedSingleton = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        sharedSingleton = [[self alloc] init];
    }
}

- (id)init {
    self = [super init];
    if (self) {
        // Member initialization here.
    }
    return self;
}
0
JJD

Vous ne voulez pas vous synchroniser sur vous-même ... Puisque l'objet de soi n'existe pas encore! Vous finissez par verrouiller une valeur d'identifiant temporaire. Vous voulez vous assurer que personne d'autre ne peut exécuter les méthodes de classe (sharedInstance, alloc, allocWithZone:, etc.), vous devez donc synchroniser sur l'objet de classe:

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end
0
Rob Dotson

Mon chemin est simple comme ça:

static id instanceOfXXX = nil;

+ (id) sharedXXX
{
    static volatile BOOL initialized = NO;

    if (!initialized)
    {
        @synchronized([XXX class])
        {
            if (!initialized)
            {
                instanceOfXXX = [[XXX alloc] init];
                initialized = YES;
            }
        }
    }

    return instanceOfXXX;
}

Si le singleton est déjà initialisé, le bloc LOCK ne sera pas entré. La deuxième vérification si (! Initialized) consiste à s'assurer qu'il n'est pas encore initialisé lorsque le thread en cours acquiert le verrou.

0
TienDC
static mySingleton *obj=nil;

@implementation mySingleton

-(id) init {
    if(obj != nil){     
        [self release];
        return obj;
    } else if(self = [super init]) {
        obj = self;
    }   
    return obj;
}

+(mySingleton*) getSharedInstance {
    @synchronized(self){
        if(obj == nil) {
            obj = [[mySingleton alloc] init];
        }
    }
    return obj;
}

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    if(obj != self){
        [super release];
    }
    //do nothing
}

- (id)autorelease {
    return self;
}

-(void) dealloc {
    [super dealloc];
}
@end
0
user370199

Je voulais juste laisser ça ici pour que je ne le perde pas. L'avantage de celui-ci est qu'il est utilisable dans InterfaceBuilder, ce qui est un énorme avantage. Ceci est tiré d'une autre question que j'ai posée :

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}
0
Dan Rosenstark

Je n'ai pas lu toutes les solutions, alors pardonnez-moi si ce code est redondant.

Ceci est la mise en œuvre la plus sécuritaire à mon avis.

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}
0
Zolt