À quoi devrait ressembler mon singleton Objective-C? [fermé]

334

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

schwa
la source
27
Ce que vous avez est bien, bien que vous puissiez déplacer la déclaration de variable globale dans votre méthode d'instance + (le seul endroit où elle doit être utilisée, à moins que vous ne l'autorisiez également à être définie) et utiliser un nom comme + defaultMyClass ou + sharedMyClass pour votre méthode. + l'instance n'est pas révélatrice d'intentions.
Chris Hanson
Puisqu'il est peu probable que la «réponse» à cette question changera de si tôt, je place un verrou historique sur la question. Deux raisons 1) Beaucoup de vues, votes et bon contenu 2) Pour empêcher le yo-yoing de l'ouvert / fermé. C'était une excellente question pour l'époque, mais les questions de ce type ne conviennent pas à Stack Overflow. Nous avons maintenant Code Review pour vérifier le code de travail. Veuillez prendre toute discussion de cette question à cette méta-question .
George Stocker

Réponses:

207

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

Le runtime envoie initializeà chaque classe d'un programme exactement une fois juste avant que la classe, ou toute classe qui en hérite, n'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 runtime envoie le initializemessage aux classes de manière thread-safe. Les superclasses reçoivent ce message avant leurs sous-classes.

Vous pourriez donc faire quelque chose de semblable à ceci:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}
Robbie Hanson
la source
7
Si le runtime ne l'appellera qu'une seule fois, que fait le BOOL? Est-ce une précaution si quelqu'un appelle explicitement cette fonction à partir de son code?
Aftermathew
5
Oui, c'est une précaution car la fonction peut également être appelée directement.
Robbie Hanson
33
Cela est également requis car il peut y avoir des sous-classes. S'ils ne remplacent pas +initializeleur super-classe, l'implémentation sera appelée si la sous-classe est d'abord utilisée.
Sven
3
@Paul, vous pouvez remplacer la releaseméthode et la rendre vide. :)
4
@aryaxt: D'après les documents répertoriés, c'est déjà thread-safe. Ainsi, l'appel est une fois par runtime - période. Cela semblerait être la solution correcte, thread-safe et efficace de manière optimale.
lilbyrdie
95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

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

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

    return sharedSingleton;
  }
}

@end

[La source]

Ben Hoffstein
la source
7
C'est tout ce que vous devez généralement utiliser pour les singletons. Entre autres choses, garder vos classes séparément instanciables les rend plus faciles à tester, car vous pouvez tester des instances distinctes au lieu d'avoir un moyen de réinitialiser leur état.
Chris Hanson
3
Stig Brautaset: Non, ce n'est pas correct de laisser de côté le @synchronized dans cet exemple. Il est là pour gérer la condition de concurrence possible de deux threads exécutant cette fonction statique en même temps, dépassant tous les deux le test "if (! SharedSingleton)" en même temps, et entraînant ainsi deux [MySingleton alloc] s. .. Le @synchronized {scope block} force ce second thread hypothétique à attendre que le premier thread quitte le {scope block} avant d'être autorisé à y accéder. J'espère que ça aide! =)
MechEthan
3
Qu'est-ce qui empêche quelqu'un de créer sa propre instance de l'objet? MySingleton *s = [[MySingelton alloc] init];
lindon fox
1
@lindonfox Quelle est la réponse à votre question?
Raffi Khatchadourian
1
@Raffi - désolé, je pense que j'ai dû oublier de coller ma réponse. Quoi qu'il en soit, j'ai eu le livre Pro Objective-C Design Patterns for iOSet il explique comment on fait un singelton "strict". Fondamentalement, puisque vous ne pouvez pas rendre les méthodes de lancement privées, vous devez remplacer les méthodes d'allocation et de copie. Donc, si vous essayez de faire quelque chose comme cela, [[MySingelton alloc] init]vous obtiendrez une erreur d'exécution (mais pas une erreur de compilation malheureusement). Je ne comprends pas comment tous les détails de la création d'objet, mais vous implémentez + (id) allocWithZone:(NSZone *)zonece qui s'appelle insharedSingleton
lindon fox
59

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;
}
Colin Barrett
la source
6
Ne vous embêtez pas avec tout ce que vous faites ci-dessus. Faites vos singletons (espérons-le extrêmement peu) séparément instanciables, et ayez juste une méthode partagée / par défaut. Ce que vous avez fait n'est nécessaire que si vous voulez vraiment, vraiment, UNIQUEMENT une seule instance de votre classe. Ce que vous ne faites pas, esp. pour les tests unitaires.
Chris Hanson
Le truc, c'est que c'est l'exemple de code Apple pour "créer un singleton". Mais oui, vous avez absolument raison.
Colin Barrett
1
L'exemple de code Apple est correct si vous voulez un "vrai" singleton (c'est-à-dire un objet qui ne peut être instancié qu'une fois, jamais) mais comme le dit Chris, c'est rarement ce que vous voulez ou avez besoin alors qu'une sorte d'instance partagée configurable est ce que vous veulent généralement.
Luke Redpath
Voici une macro pour la méthode ci-dessus: gist.github.com/1057420 . C'est ce que j'utilise.
Kobski
1
Mis à part les tests unitaires, il n'y a rien contre cette solution, n'est-ce pas? Et c'est rapide et sûr.
LearnCocos2D
58

Depuis que Kendall a publié un singleton threadsafe qui tente d'éviter les coûts de verrouillage, 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;                                                                        
}

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

  1. Cas rapide: En exécution normale sharedInstancea déjà été définie, donc la whileboucle n'est jamais exécutée et la fonction revient après avoir simplement testé l'existence de la variable;

  2. Cas lent: S'il sharedInstancen'existe pas, alors une instance est allouée et copiée dedans à l'aide d'un Compare And Swap ('CAS');

  3. Cas envisagé: si deux threads tentent tous les deux d'appeler sharedInstanceen même temps ET sharedInstance n'existent pas en même temps, ils initialiseront tous les deux de nouvelles instances du singleton et tenteront de le mettre en position CAS. Celui qui gagne le CAS retourne immédiatement, celui qui perd libère l'instance qu'il vient d'allouer et retourne le (maintenant défini) sharedInstance. Le single OSAtomicCompareAndSwapPtrBarrieragit à la fois comme barrière d'écriture pour le thread de configuration et comme barrière de lecture depuis le thread de test.

Louis Gerbarg
la source
18
Il s'agit d'une surpuissance totale pour au maximum une fois pendant la durée de vie d'une application. Néanmoins, il est correct, et la technique de comparaison et d'échange est un outil utile à connaître, donc +1.
Steve Madsen
Bonne réponse - la famille OSAtomic est une bonne chose à savoir
Bill
1
@Louis: Réponse incroyable et vraiment éclairante! Une question cependant: que devrait faire ma initméthode dans votre approche? Jeter une exception quand sharedInstanceest initialisé n'est pas une bonne idée, je crois. Que faire alors pour empêcher l'utilisateur d'appeler initdirectement plusieurs fois?
matm
2
Je ne l'empêche généralement pas. Il y a souvent des raisons valables pour permettre à ce qui est généralement un singleton de se multiplier, le plus commun étant pour certains types de tests unitaires. Si je voulais vraiment appliquer une seule instance, j'aurais probablement la vérification de la méthode init pour voir si le global existait, et si c'était le cas, je le fais se libérer et retourner le global.
Louis Gerbarg
1
@Tony bit en retard de réponse, mais OSAtomicCompareAndSwapPtrBarrier nécessite un volatile. Peut-être que le mot clé volatile est d'empêcher le compilateur d'optimiser la vérification? Voir: stackoverflow.com/a/5334727/449161 et developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn
14
statique MyClass * sharedInst = nil;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == nil) {
            / * sharedInst configuré dans init * /
            [[self alloc] init];
        }
    }
    return sharedInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [Augmentation NSException: NSInternalInconsistencyException
            format: @ "[% @% @] ne peut pas être appelé; utilisez + [% @% @] à la place"],
            NSStringFromClass ([self class]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([self class]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = self;
        / * Quelle que soit la classe spécifique ici * /
    }
    return sharedInst;
}

/ * Ceux-ci ne font probablement rien dans
   une application GC. Garde singleton
   comme un singleton réel dans un
   application non CG
* /
- (NSUInteger) retenueCompte
{
    return NSUIntegerMax;
}

- (oneway void) libération
{
}

- (id) conserver
{
    return sharedInst;
}

- (id) autorelease
{
    return sharedInst;
}

la source
3
J'ai remarqué que clang se plaint d'une fuite si vous n'affectez pas le résultat de [[self alloc] init]à sharedInst.
pix0r
Subvertir init comme celui-ci est une approche assez moche de l'OMI. Ne jouez pas avec init et / ou la création réelle de l'objet. Si vous optez plutôt pour un point d'accès contrôlé à une instance partagée, alors que vous n'insérez pas singleton dans l'objet, vous aurez plus de plaisir plus tard si vous écrivez des tests, etc. Les singletons durs sont beaucoup trop utilisés.
occulus
12

Edit: Cette implémentation obsolète avec ARC. Veuillez consulter Comment mettre en œuvre un singleton Objective-C compatible avec ARC? pour une mise en œuvre correcte.

Toutes les implémentations d'initialisation 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 via 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 entraînant un deuxième 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, 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 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'assertion; ou simplement NSAssert.)

lorean
la source
1
Je voudrais simplement vivre plus simplement et éviter d'initialiser complètement.
Tom Andersen
9

J'ai une variation intéressante sur sharedInstance qui est thread-safe, mais ne se verrouille pas après l'initialisation. Je n'en suis pas encore assez sûr pour modifier la première réponse 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;
}
Kendall Helmstetter Gelner
la source
1
+1 c'est vraiment intrigant. Je pourrais utiliser class_replaceMethodpour me transformer sharedInstanceen clone de simpleSharedInstance. De cette façon, vous n'aurez plus jamais à vous soucier d'acquérir une @synchronizedserrure.
Dave DeLong
C'est le même effet, utiliser exchangeImplementations signifie qu'après init lorsque vous appelez sharedInstance, vous appelez vraiment simpleSharedInstance. En fait, j'ai commencé avec replaceMethod, mais j'ai décidé qu'il valait mieux changer les implémentations pour que l'original existe toujours si nécessaire ...
Kendall Helmstetter Gelner
Lors de tests supplémentaires, je n'ai pas réussi à faire remplacer replaceMethod - dans les appels répétés, le code appelait toujours l'original sharedInstance au lieu de simpleSharedInstance. Je pense que c'est peut-être parce que ce sont deux méthodes au niveau de la classe ... Le remplacement que j'ai utilisé était: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); et certaines de ses variantes. Je peux vérifier que le code que j'ai publié fonctionne et simpleSharedInstance est appelé après le premier passage via sharedInstance.
Kendall Helmstetter Gelner
Vous pouvez créer une version thread-safe qui ne paie pas les frais de verrouillage après l'initialisation sans faire un tas de déblayage d'exécution, j'ai posté une implémentation ci-dessous.
Louis Gerbarg
1
+1 bonne idée. J'adore ces choses que l'on peut faire avec le runtime. Mais dans la plupart des cas, il s'agit probablement d'une optimisation prématurée. Si je devais vraiment me débarrasser du coût de synchronisation, j'utiliserais probablement la version sans verrou de Louis.
Sven
6

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 documents ou la page de manuel.

quellish
la source
5

J'ai roulé singleton dans une classe, afin que d'autres classes puissent 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 d'une classe, que vous voulez devenir singleton.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

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

obscenum
la source
Vous voudrez peut-être utiliser un autre mécanisme de verrouillage car il @synchronizedest horriblement lent et doit être évité.
DarkDust
2

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
lajos
la source
2

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;
}
Jompe
la source
2
La technique de verrouillage à double vérification utilisée ici est souvent un vrai problème dans certains environnements (voir aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf ou Google it). Jusqu'à preuve du contraire, je suppose que Objective-C n'est pas immunisé. Voir également wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen
2

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 ainsi le coût de synchronisation après l'initialisation?

Tony
la source
Voir les discussions sur le verrouillage à double vérification dans d'autres réponses.
i_am_jorf
1

KLSingleton c'est:

  1. Sous-classe (au nième degré)
  2. Compatible ARC
  3. Sûr avec allocetinit
  4. Chargé paresseusement
  5. Thread-safe
  6. Sans verrouillage (utilise + initialiser, pas @synchronize)
  7. Sans macro
  8. Sans swizzle
  9. Facile

KLSingleton

kevinlawler
la source
1
J'utilise votre NSSingleton pour mon projet, et il semble être incompatible avec KVO. Le problème est que KVO crée une sous-classe pour chaque objet KVO avec le préfixe NSKVONotifying_ MyClass . Et cela fait que les méthodes MyClass + initialize et -init sont appelées deux fois.
Oleg Trakhman
J'ai testé cela sur le dernier Xcode et je n'ai eu aucun problème à m'inscrire ou à recevoir des événements KVO. Vous pouvez le vérifier avec le code suivant: gist.github.com/3065038 Comme je l'ai mentionné sur Twitter, les méthodes + initialize sont appelées une fois pour NSSingleton et une fois pour chaque sous-classe. Ceci est une propriété d'Objective-C.
kevinlawler
Si vous ajoutez NSLog(@"initialize: %@", NSStringFromClass([self class]));à la +initializeméthode, vous pouvez vérifier que les classes ne sont initialisées qu'une seule fois.
kevinlawler
NSLog (@ "initialize:% @", NSStringFromClass ([self class]));
Oleg Trakhman
Vous voudrez peut-être également qu'il soit compatible IB. Le mien est: stackoverflow.com/questions/4609609/…
Dan Rosenstark
0

Vous ne voulez pas vous synchroniser sur vous-même ... Puisque le self-objet n'existe pas encore! Vous finissez par verrouiller une valeur d'ID 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 vous synchroniser sur l'objet 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
Rob Dotson
la source
1
Les autres méthodes, méthodes d'accesseur, méthodes de mutation, etc. doivent se synchroniser sur elles-mêmes. Toutes les méthodes et initialiseurs de classe (+) (et probablement -dealloc) doivent se synchroniser sur l'objet classe. Vous pouvez éviter d'avoir à synchroniser manuellement si vous utilisez des propriétés Objective-C 2.0 au lieu des méthodes d'accesseur / mutateur. Tous les objets object.property et object.property = foo sont automatiquement synchronisés avec self.
Rob Dotson
3
Veuillez expliquer pourquoi vous pensez que l' selfobjet n'existe pas dans une méthode de classe. Le runtime détermine quelle implémentation de méthode appeler en fonction de la même valeur exacte qu'il fournit selfpour chaque méthode (classe ou instance).
dreamlax
2
À l'intérieur d'une méthode de classe, se self trouve l'objet de classe. Essayez-le vous-même:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
jscs
0

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 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;
}
Dan Rosenstark
la source
0
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
user370199
la source
0

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 considérablement le singleton.

Voici les macros que j'ai écrites sur la base 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 quand 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 des méthodes plus automatiques ou les changer.

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 la rédaction).

Nate
la source
0

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 enveloppant la classe dans une autre classe qui a juste des 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 aucune instance!

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

chunkyguy
la source
Votre lien ne fonctionne plus.
i_am_jorf
0

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;
}
JJD
la source
0

Ma façon 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 (! Initialisé) est de s'assurer qu'il n'est pas encore initialisé lorsque le thread actuel acquiert le LOCK.

TienDC
la source
Il n'est pas clair que le marquage initializedcomme volatilesuffisant soit suffisant. Voir aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf
0

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

À mon avis, c'est la mise en œuvre la plus sûre pour les threads.

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

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

    return sharedResourcesObj;
}
Zolt
la source
-4

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

Cependant, j'ai parfois besoin de placer un singleton dans une 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 récupération de place.

Gregory Higley
la source
2
Votre code n'est pas thread-safe. Il utilise synchronisé dans la méthode alloc, mais pas dans la méthode init. Vérifier le booléen initialisé n'est pas thread-safe.
Mecki
-5

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

Selon la 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 d'auto fonctionne, cela ne devrait pas et cela ressemble à une erreur de copier-coller pour moi. L'implémentation correcte d'une méthode de fabrique de classe serait:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
utilisateur supprimé
la source
6
auto certainement ne fait exister ce champ de classe. Il fait référence à la classe au lieu de l'instance de la classe. Les classes sont (principalement) des objets de première classe.
schwa
Pourquoi mettez-vous @synchroninzed WITHIN dans une méthode?
user4951
1
Comme schwa l'a déjà dit, self est l'objet de classe à l'intérieur d'une méthode de classe. Voir mon commentaire pour un extrait démontrant cela.
jscs
selfexiste, mais son utilisation comme identifiant transmis @synchronizedsynchronisera l'accès aux méthodes de l'instance. Comme le souligne @ user490696, il existe des cas (comme des singletons) où l'utilisation de l'objet classe est préférable. Du guide de programmation Obj-C:You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
quellish