design-patterns swift - À quoi devrait ressembler mon singleton Objective-C?




sharedinstance class (22)

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

C'est l'implémentation la plus sûre à mon avis.

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

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

    return sharedResourcesObj;
}

Ma méthode accesseur 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?


J'ai déroulé singleton dans une classe, donc les autres classes peuvent hériter des propriétés 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, que vous voulez devenir singleton.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

La seule limitation à propos de la classe Singleton est que c'est une sous-classe NSObject. Mais la plupart du temps j'utilise des singletons dans mon code, ils sont en fait des sous-classes NSObject, donc cette classe facilite vraiment ma vie et rend le code plus propre.


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

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 en fonction de plusieurs implémentations d'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 quand elle est presque vide? Cohérence de code entre l'en-tête et les fichiers de code; maintenabilité dans le cas où vous souhaitez ajouter des méthodes plus automatiques ou le modifier.

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



Ne devrait-il pas être sécurisé 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;
}

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 if (! Initialized) est de s'assurer qu'il n'est pas encore initialisé quand le thread en cours acquiert le LOCK.


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



Vous ne voulez pas synchroniser sur soi ... Depuis que l'objet self n'existe pas encore! Vous finissez par vous verrouiller sur une valeur d'ID temporaire. Vous voulez vous assurer que personne d'autre ne peut exécuter des méthodes de classe (sharedInstance, alloc, allocWithZone :, etc), vous devez donc synchroniser sur l'objet de classe à la place:

@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

Edit: cette implémentation est obsolète avec ARC. Jetez un coup d'œil à Comment puis-je implémenter un singleton Objective-C compatible avec ARC? pour une mise en œuvre 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 d'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ù les 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 résultant en un second déclenchement de +initialize .

Vous pouvez penser que vous devez vérifier implicitement 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 allez vous tirer dans le pied; ou pire, donner à 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 ma mise en œuvre

@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'assertion ou NSAssert.)


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

KLSingleton est:

  1. Sous-classe (au nième degré)
  2. Compatible ARC
  3. Sécurisé avec alloc et init
  4. Chargé paresseusement
  5. Fil sécurisé
  6. Lock-free (utilise + initialize, pas @synchronize)
  7. Macro-libre
  8. Sans swizzle
  9. Simple

KLSingleton


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

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

Je voulais juste laisser ça ici pour ne pas le perdre. L'avantage de celui-ci est qu'il est utilisable dans InterfaceBuilder, ce qui est un énorme avantage. Ceci est pris 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;
}

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

[[Librarian sharedInstance] openLibrary]

à:

[Librarian openLibrary]

En encapsulant la classe dans une autre classe qui n'a que des méthodes de classe , il n'y a aucune chance de créer accidentellement des instances dupliquées, car nous ne créons aucune instance!

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


Cela fonctionne également dans un environnement non-garbage collecté.

@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

Depuis que Kendall a posté un singleton threadsafe qui tente d'éviter les coûts de blocage, j'ai pensé que j'en jetterais 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, ainsi la boucle while n'est jamais exécutée et la fonction retourne après avoir simplement testé l'existence de la variable;

  2. Slow case: Si sharedInstance n'existe pas, une instance est allouée et copiée à l'aide d'un comparateur et d'un swap ('CAS');

  3. Cas sharedInstance : Si deux threads tentent tous deux d'appeler sharedInstance en même temps ET sharedInstance n'existe pas en même temps, ils vont à la fois initialiser de nouvelles instances du singleton et tenter de le mettre en position. Celui qui gagne le CAS retourne immédiatement, celui qui perd gagne l'instance qu'il vient d'allouer et renvoie l' sharedInstance (maintenant définie). Le seul OSAtomicCompareAndSwapPtrBarrier agit à la fois comme une barrière d'écriture pour le thread de réglage et comme une barrière de lecture du thread de test.


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

Cependant, parfois j'ai besoin de placer un singleton dans un NIB, et 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 de collecte des ordures.


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

Le moteur d'exécution envoie une initialize à chaque classe dans un programme exactement une fois juste avant que la classe, ou toute classe qui en hérite, envoie 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 comme ça:

static MySingleton *sharedSingleton;

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

Je suis d'accord avec NilObject , mais je voudrais ajouter ceci:

Si vous vous trouvez en train de copier une méthode entière et que vous ne modifiez qu'une petite partie du code, vous pouvez envisager d'y remédier en inversant le contrôle

Si vous vous retrouvez à copier et coller du code, vous faites presque toujours quelque chose de mal. Codifié comme principe de conception Une fois et une seule fois .







objective-c design-patterns singleton object-initializers