Question

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 puis-je faire pour améliorer cela?

Était-ce utile?

La solution

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.

Vous pouvez donc faire quelque chose qui ressemble à ceci:

static MySingleton *sharedSingleton;

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

Autres conseils

@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]

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;
}

Depuis publié par un threadsafe singleton qui tente d’éviter le blocage des coûts, je pensais en jeter 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;                                                                        
}

D'accord, laissez-moi vous expliquer comment cela fonctionne:

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

  2. Cas lent: si instance partagée n'existe pas, une instance est allouée et copiée dans celle-ci à l'aide d'un comparateur et échange ('CAS');

  3. Cas concerné: Si deux threads tentent tous deux d'appeler instance partagée en même temps, ET instance partagée n'existe pas à la En même temps, ils initialiseront de nouvelles instances du singleton et tenteront de le placer en position de secours. 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 instance partagée (maintenant défini). L'unique OSAtomicCompareAndSwapPtrBarrier sert à la fois de barrière en écriture pour le thread de paramétrage et de barrière de lecture en provenance du thread de test.

static MyClass *sharedInst = nil;

+ (id)sharedInstance
{
    @synchronize( self ) {
        if ( sharedInst == nil ) {
            /* sharedInst set up in init */
            [[self alloc] init];
        }
    }
    return sharedInst;
}

- (id)init
{
    if ( sharedInst != nil ) {
        [NSException raise:NSInternalInconsistencyException
            format:@"[%@ %@] cannot be called; use +[%@ %@] instead"],
            NSStringFromClass([self class]), NSStringFromSelector(_cmd), 
            NSStringFromClass([self class]),
            NSStringFromSelector(@selector(sharedInstance)"];
    } else if ( self = [super init] ) {
        sharedInst = self;
        /* Whatever class specific here */
    }
    return sharedInst;
}

/* These probably do nothing in
   a GC app.  Keeps singleton
   as an actual singleton in a
   non CG app
*/
- (NSUInteger)retainCount
{
    return NSUIntegerMax;
}

- (oneway void)release
{
}

- (id)retain
{
    return sharedInst;
}

- (id)autorelease
{
    return sharedInst;
}

Modifier: cette implémentation est obsolète avec ARC. Veuillez consulter 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 deuxième déclenchement de + initialize .

Vous pouvez penser que vous devez 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.)

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 .

J'ai une variation 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 l'en-tête dispatch / once.h pour comprendre. Que se passe-t-il. Dans ce cas, les commentaires d’en-tête sont plus applicables que la documentation ou la page de manuel.

J'ai transféré 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, ce sont en fait des sous-classes NSObject. Cette classe facilite donc vraiment ma vie et rend le code plus propre.

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

Cela ne devrait-il pas être sécuritaire pour les threads et éviter les coûts élevés liés au premier appel?

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

Que diriez-vous de

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?

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

Utiliser le modèle Singleton dans Objective-C

  

KLSingleton est:

     
      
  1. Sous-classifiable (au nième degré)
  2.   
  3. compatible ARC
  4.   
  5. Coffre-fort avec alloc et init
  6.   
  7. Chargé paresseusement
  8.   
  9. Fil-safe
  10.   
  11. Sans verrouillage (utilise + initialize, pas @synchronize)
  12.   
  13. Sans macro
  14.   
  15. Sans balourd
  16.   
  17. Simple
  18.   

KLSingleton

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 effectuer une synchronisation 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

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;
}
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

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 l'en-tête et les fichiers 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 la rédaction).

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 contenant des méthodes de classe , vous éviterez ainsi la création accidentelle d'instances en double, car nous ne créons aucune instance!

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

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;
}

Mon chemin est simple comme ceci:

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.

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

À mon avis, il s'agit de la mise en œuvre la plus sécurisée en termes de threads.

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

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

    return sharedResourcesObj;
}

J'utilise généralement un code similaire à celui de la réponse de Ben Hoffstein (que j'ai également tiré de Wikipedia). Je l'utilise pour les raisons exposées par Chris Hanson dans son commentaire.

Cependant, j'ai parfois besoin de placer un singleton dans une carte d'interface réseau. Dans ce cas, j'utilise ce qui suit:

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

Je laisse l'implémentation de -retain (etc.) au lecteur, bien que le code ci-dessus soit tout ce dont vous avez besoin dans un environnement mal rangé.

La réponse acceptée, bien qu'elle compile, est incorrecte.

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

Documentation Apple:

... Vous pouvez adopter une approche similaire pour synchroniser les méthodes de classe de la classe associée, en utilisant l'objet Class au lieu de self.

Même si l’utilisation de self fonctionne, cela ne devrait pas et cela ressemble à une erreur de copier / coller pour moi. La mise en œuvre correcte pour une méthode de fabrique de classe serait:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top