web-dev-qa-db-fra.com

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

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?

168
cescofry

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

+ (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;
}
377
Nick Forge

si vous voulez créer une autre instance selon vos besoins.do ceci:

+ (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;
}
8
DongXu

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
5
Igor

Ceci est mon modèle sous ARC. Satisfait le nouveau modèle utilisant GCD et é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
2
Eonil

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 la solution - du tout!

Pour créer un singleton avec succès, vous devez pouvoir effectuer les opérations suivantes 3:

  • S'il y avait une condition de concurrence , nous ne devons pas autoriser la création simultanée de plusieurs instances de votre SharedInstance!
  • Rappelez-vous et conservez la valeur parmi plusieurs invocations.
  • Créez-le une seule fois. En contrôlant le point d'entrée.

dispatch_once_t Vous aide à résoudre une condition de concurrence en ne permettant que l'envoi de son blocage.

Static vous aide à "mémoriser" sa valeur pour un nombre quelconque d'appels. Comment se souvient-il? Il ne permet à aucune nouvelle instance portant ce nom exact de votre instance partagée d'être recréé, elle fonctionne uniquement avec celle créée à l'origine.

N'utilise pas l'appel de allocinit (c'est-à-dire que nous avons toujours allocinit méthodes puisque nous sommes une sous-classe NSObject, bien que nous ne devions PAS les utiliser) sur notre classe sharedInstance, nous y parvenons en utilisant +(instancetype)sharedInstance, qui est lié à être initié une fois , indépendamment des multiples tentatives de différents threads en même temps et mémorise sa valeur.

Certains des singletons système les plus courants fournis avec Cocoa sont:

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

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

2
Honey

Objective-C fournit également 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 seul environnement iOS 6 et dispatch_once est apparu dans les cadres de la pile.

1
Walt Sellers

Classe Singleton: Personne ne peut créer plus d'un objet de classe, 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];
}
0
Yogi

La réponse acceptée pose deux problèmes, qui peuvent ou non être pertinents pour votre propos.

  1. Si, à partir de la méthode init, la méthode sharedInstance est à nouveau appelée (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 traite 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;
}
0
Werner Altewischer