objective-c - sharedinstance - singleton swift 4




À quoi devrait ressembler mon singleton Objective-C? (18)

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

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?


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 :)


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'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.


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.


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

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

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

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

Per Apple documentation:

... You can take a similar approach to synchronize the class methods of the associated class, using the Class object instead of self.

Even if using self works, it shouldn't and this looks like a copy and paste mistake to me. The correct implementation for a class factory method would be:

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

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

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

Que diriez-vous

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Donc vous évitez le coût de synchronisation après l'initialisation?


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 ce qui se passe. Dans ce cas, les commentaires d'en-tête sont plus applicables que les docs ou la page de manuel.


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



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.)


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


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






object-initializers