Comment implémenter un singleton Objective-C compatible avec ARC?

172

Comment convertir (ou créer) une classe singleton qui se compile et se comporte correctement lors de l'utilisation du comptage automatique de références (ARC) dans Xcode 4.2?

cescofry
la source
1
J'ai récemment trouvé un article de Matt Galloway assez approfondi sur les Singletons pour les environnements ARC et de gestion manuelle de la mémoire. galloway.me.uk/tutorials/singleton-classes
cescofry

Réponses:

391

De la même manière que vous (auriez dû) déjà le faire:

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}
Nick Forge
la source
9
Vous ne faites tout simplement pas la gestion de la mémoire hokey pokey qu'Apple recommandait dans developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
Christopher Pickslay
1
@MakingScienceFictionFact, vous voudrez peut-être jeter un coup d'œil à cet article
kervich
6
Les staticvariables @David déclarées dans une méthode / fonction sont les mêmes qu'une staticvariable déclarée en dehors d'une méthode / fonction, elles ne sont valides que dans le cadre de cette méthode / fonction. Chaque exécution séparée de la +sharedInstanceméthode (même sur des threads différents) "verra" la même sharedInstancevariable.
Nick Forge
9
Et si quelqu'un appelle [[MyClass alloc] init]? Cela créerait un nouvel objet. Comment pouvons-nous éviter cela (autre que de déclarer statique MyClass * sharedInstance = nil en dehors de la méthode).
Ricardo Sanchez-Saez
2
Si un autre programmeur se trompe et appelle init alors qu'il aurait dû appeler sharedInstance ou similaire, c'est son erreur. Subvertir les principes fondamentaux et les contrats de base de la langue afin d'empêcher les autres de commettre des erreurs semble tout à fait faux. Il y a plus de discussion sur boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus
8

si vous souhaitez créer une autre instance selon vos besoins, procédez comme suit:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

sinon, vous devriez faire ceci:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}
DongXu
la source
1
Vrai / Faux: Le dispatch_once()bit signifie que vous n'obtiendrez pas d'instances supplémentaires, même dans le premier exemple ...?
Olie
4
@Olie: Faux, car le code client peut faire [[MyClass alloc] init]et contourner l' sharedInstanceaccès. DongXu, vous devriez regarder l'article Singleton de Peter Hosey . Si vous prévoyez de remplacer allocWithZone:pour empêcher la création de plus d'instances, vous devez également le remplacer initpour empêcher la réinitialisation de l'instance partagée.
jscs
Ok, c'est ce que je pensais, d'où la allocWithZone:version. THX.
Olie
2
Cela rompt complètement le contrat d'allocWithZone.
occulus
1
singleton signifie simplement "un seul objet en mémoire à la fois", c'est une chose, être réinitialisé en est une autre.
DongXu
5

Ceci est une version pour ARC et non-ARC

Comment utiliser:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end
Igor
la source
2

C'est mon modèle sous ARC. Satisfait au nouveau modèle utilisant GCD et satisfait également l'ancien modèle de prévention d'instanciation d'Apple.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end
éonil
la source
1
N'est-ce pas c1une instance de AAAla superclasse de 's? Vous devez appeler +allocsur self, pas super.
Nick Forge
@NickForge superne signifie pas l'objet de super-classe. Vous ne pouvez pas obtenir d'objet de super-classe. Cela signifie simplement acheminer les messages vers la version de super-classe de method. superpoints encore selfclasse. Si vous souhaitez obtenir un objet de super classe, vous devez obtenir des fonctions de réflexion d'exécution.
eonil
@NickForge And -allocWithZone:method n'est qu'une simple chaîne à la fonction d'allocation du runtime pour offrir un point de remplacement. Donc finalement, selfpointer == objet de classe courant sera passé à l'allocateur, et finalement l' AAAinstance sera allouée.
eonil
vous avez raison, j'avais oublié les subtilités du superfonctionnement des méthodes de classe.
Nick Forge
N'oubliez pas d'utiliser #import <objc / objc-runtime.h>
Ryan Heitner
2

Lisez cette réponse, puis lisez l'autre réponse.

Vous devez d'abord savoir ce que signifie un Singleton et quelles sont ses exigences, si vous ne le comprenez pas, vous ne comprendrez pas du tout la solution!

Pour créer un Singleton avec succès, vous devez être capable de faire les 3 suivantes:

  • S'il y avait une condition de concurrence, nous ne devons pas autoriser la création de plusieurs instances de votre SharedInstance en même temps!
  • Souvenez-vous et conservez la valeur entre plusieurs appels.
  • Créez-le une seule fois. En contrôlant le point d'entrée.

dispatch_once_tvous aide à résoudre une condition de concurrence en n'autorisant l'envoi de son bloc qu'une seule fois.

Staticvous aide à «vous souvenir» de sa valeur à travers un nombre quelconque d'appels. Comment se souvient-il? Il ne permet pas de créer à nouveau une nouvelle instance portant le nom exact de votre sharedInstance, elle fonctionne uniquement avec celle qui a été créée à l'origine.

Ne pas utiliser d' appel alloc init(c'est-à-dire que nous avons toujours des alloc initméthodes puisque nous sommes une sous-classe NSObject, bien que nous ne devrions PAS les utiliser) sur notre classe sharedInstance, nous y parvenons en utilisant +(instancetype)sharedInstance, qui est limité pour être lancé une seule fois , quelles que soient les tentatives multiples de différents threads en même temps et rappelez-vous sa valeur.

Certains des singletons système les plus courants fournis avec Cocoa lui-même sont:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

Fondamentalement, tout ce qui aurait besoin d'avoir un effet centralisé devrait suivre une sorte de modèle de conception Singleton.

Mon chéri
la source
1

Alternativement, Objective-C fournit la méthode d'initialisation + (void) pour NSObject et toutes ses sous-classes. Il est toujours appelé avant toute méthode de la classe.

J'ai défini un point d'arrêt dans un une fois dans iOS 6 et dispatch_once est apparu dans les cadres de la pile.

Vendeurs Walt
la source
0

Classe Singleton: Personne ne peut créer plus d'un objet de classe dans tous les cas ou de quelque manière que ce soit.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}
Yogi
la source
1
Si quelqu'un appelle init, init appellera sharedInstance, sharedInstance appellera init, init appellera sharedInstance une seconde fois, puis plantera! Tout d'abord, il s'agit d'une boucle de récursivité infinie. Deuxièmement, la deuxième itération de l'appel de dispatch_once plantera car elle ne peut pas être appelée à nouveau de l'intérieur de dispatch_once.
Chuck Krutsinger le
0

Il y a deux problèmes avec la réponse acceptée, qui peuvent ou non être pertinents pour votre objectif.

  1. Si à partir de la méthode init, la méthode sharedInstance est appelée à nouveau (par exemple parce que d'autres objets sont construits à partir de là et utilisent le singleton), cela provoquera un débordement de pile.
  2. Pour les hiérarchies de classes, il n'y a qu'un seul singleton (à savoir: la première classe de la hiérarchie sur laquelle la méthode sharedInstance a été appelée), au lieu d'un singleton par classe concrète dans la hiérarchie.

Le code suivant prend en charge ces deux problèmes:

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackoverflow problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}
Werner Altewischer
la source
-2
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

J'espère que le code ci-dessus l'aidera.

Kiran
la source
-2

si vous avez besoin de créer un singleton en swift,

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

ou

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

vous pouvez utiliser de cette façon

let sharedClass = LibraryAPI.sharedInstance
Muhammedkasva
la source