web-dev-qa-db-fra.com

Corrigez l'objectif de motif singleton C (iOS)?

J'ai trouvé des informations sur le Net pour créer une classe Singleton à l'aide de GCD. C'est cool parce que c'est le fil-en sécurité avec des frais généraux très bas. Malheureusement, je n'ai pas pu trouver des solutions complètes mais seulement des extraits de la méthode SharedInstance. J'ai donc fait ma propre classe en utilisant la méthode d'essai et d'erreur - et et voici - ce qui suit est sorti:

@implementation MySingleton

// MARK: -
// MARK: Singleton Pattern using GCD

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; }
- (id)copyWithZone:(NSZone *)zone { return self; }
- (id)autorelease { return self; }
- (oneway void)release { /* Singletons can't be released */ }
- (void)dealloc { [super dealloc]; /* should never be called */ }
- (id)retain { return self; }
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ }

+ (MySingleton *)sharedInstance
{
    static MySingleton * instance = nil;

    static dispatch_once_t predicate;   
    dispatch_once(&predicate, ^{
        // --- call to super avoids a deadlock with the above allocWithZone
        instance = [[super allocWithZone:nil] init];
    });

    return instance;
}

// MARK: -
// MARK: Initialization

- (id)init
{
    self = [super init];
    if (self) 
    {
        // Initialization code here.
    }
    return self;
}

@end

S'il vous plaît n'hésitez pas à commenter et dites-moi si je manque quelque chose ou fais quelque chose de complètement faux;)

Bravo Stefan

29
blackjacx

Rester simple:

+(instancetype)sharedInstance
{
    static dispatch_once_t pred;
    static id sharedInstance = nil;
    dispatch_once(&pred, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (void)dealloc
{
    // implement -dealloc & remove abort() when refactoring for
    // non-singleton use.
    abort();
}

C'est ça. Remplacer retain, release, retainCount _ et le reste consiste à cacher des bogues et à ajouter un tas de lignes de code inutile. Chaque ligne de code est un bug qui attend peut arriver. En réalité, si vous causez dealloc à appeler sur votre instance partagée, vous avez un bogue très grave dans votre application. Ce bug doit être corrigé, non caché.

Cette approche se prête également à refactoriser pour soutenir les modes d'utilisation non singleton. À peu près, chaque singleton qui survit au-delà de quelques sorties sera éventuellement refouturé dans une forme non singleton. Certains (comme NSFileManager) continuent de soutenir un mode singleton tout en soutenant instanciation arbitraire.

Notez que ce qui précède "fonctionne simplement" en arc.

82
bbum
// See Mike Ash "Care and Feeding of Singletons"
// See Cocoa Samurai "Singletons: You're doing them wrong"
+(MySingleton *)singleton {
    static dispatch_once_t pred;
    static MySingleton *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[MySingleton alloc] init];
        shared.someIvar = @"blah";
    });
    return shared;
}

Sachez que dispatch_once n'est pas rentrante , donc qui se fait appeler à l'intérieur du bloc de dispatch_once va se bloquer le programme.

Ne tentez pas de manière défensive contre vous-même. Si vous n'êtes pas le codage d'un cadre, traiter votre classe comme d'habitude puis coller l'idiome singleton ci-dessus. Pensez à l'idiome singleton comme méthode pratique, non pas comme un trait déterminant de votre classe. Vous voulez traiter votre classe comme une classe normale lors de tests unitaires, il est donc OK pour laisser un constructeur accessible.

Ne vous embêtez pas avec allocWithZone:

  • Il ignore son argument et se comporte exactement comme alloc. Zones de mémoire ne sont plus utilisés dans Objective-C si allocWithZone: ne sont conservés que pour la compatibilité avec l'ancien code.
  • Il ne fonctionne pas. Vous ne pouvez pas appliquer le comportement singleton en Objective-C, car plusieurs instances peuvent toujours être créés à l'aide NSAllocateObject() et class_createInstance().

Une méthode de fabrication singleton renvoie toujours l'un de ces trois types:

  • id pour indiquer le type de retour est pas complètement connue (cas où vous construisez un cluster de classe).
  • instancetype pour indiquer que le type de retour est une instance de la classe englobante.
  • Le nom de la classe elle-même (MySingleton dans l'exemple) pour le maintenir simple.

Depuis que vous avez taguée cette iOS, une alternative à un singleton sauve le Ivar au délégué de l'application, puis en utilisant une macro de commodité que vous pouvez redéfinir si vous changez d'avis:

#define coreDataManager() \
        ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager
19
Jano

Si vous souhaitez tester votre appareil à tester votre singleton, vous devez également le faire pour que vous puissiez le remplacer par une simulation singleton et/ou le réinitialiser à la normale:

@implementation ArticleManager

static ArticleManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;

+(ArticleManager *)sharedInstance {
    dispatch_once(&once_token, ^{
        if (_sharedInstance == nil) {
            _sharedInstance = [[ArticleManager alloc] init];
        }
    });
    return _sharedInstance;
}

+(void)setSharedInstance:(ArticleManager *)instance {
    once_token = 0; // resets the once_token so dispatch_once will run again
    _sharedInstance = instance;
}

@end
1
ToddH