Si vous pouvez cibler iOS 4.0 ou supérieur
En utilisant GCD, est-ce le meilleur moyen de créer un singleton en Objective-C (thread safe)?
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
C'est un moyen parfaitement acceptable et thread-safe pour créer une instance de votre classe. Techniquement, ce n'est peut-être pas un "singleton" (en ce sens qu'il ne peut jamais y avoir qu'un seul de ces objets), mais tant que vous utilisez uniquement la méthode [Foo sharedFoo]
pour accéder à l'objet, cela suffit.
instancetype
n'est qu'une des nombreuses extensions de langage de Objective-C
, d'autres étant ajoutées à chaque nouvelle version.
Sache-le, aime-le.
Et prenez-le comme exemple de la manière dont l'attention portée aux détails de bas niveau peut vous donner un aperçu de nouveaux moyens puissants de transformer Objective-C.
référez-vous ici: instancetype
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^
{
sharedInstance = [self new];
});
return sharedInstance;
}
+ (Class*)sharedInstance
{
static dispatch_once_t once;
static Class *sharedInstance;
dispatch_once(&once, ^
{
sharedInstance = [self new];
});
return sharedInstance;
}
MySingleton.h
@interface MySingleton : NSObject
+(instancetype)sharedInstance;
+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));
@end
MySingleton.m
@implementation MySingleton
+(instancetype)sharedInstance {
static dispatch_once_t pred;
static id shared = nil;
dispatch_once(&pred, ^{
shared = [[super alloc] initUniqueInstance];
});
return shared;
}
-(instancetype)initUniqueInstance {
return [super init];
}
@end
Vous pouvez éviter que la classe soit allouée en écrasant la méthode alloc.
@implementation MyClass
static BOOL useinside = NO;
static id _sharedObject = nil;
+(id) alloc {
if (!useinside) {
@throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
}
else {
return [super alloc];
}
}
+(id)sharedInstance
{
static dispatch_once_t p = 0;
dispatch_once(&p, ^{
useinside = YES;
_sharedObject = [[MyClass alloc] init];
useinside = NO;
});
// returns the same object each time
return _sharedObject;
}
Dave a raison, c'est très bien. Vous souhaiterez peut-être consulter documentation d'Apple sur la création d'un singleton pour obtenir des conseils sur la mise en œuvre de certaines des autres méthodes afin de garantir la création d'une seule si les classes choisissent de ne PAS utiliser la méthode sharedFoo.
Si vous voulez vous assurer que [[MyClass alloc] init] renvoie le même objet que sharedInstance (ce qui n'est pas nécessaire à mon avis, mais certains le veulent), vous pouvez le faire très facilement et en toute sécurité à l'aide d'un second dispatch_once:
- (instancetype)init
{
static dispatch_once_t once;
static Class *sharedInstance;
dispatch_once(&once, ^
{
// Your normal init code goes here.
sharedInstance = self;
});
return sharedInstance;
}
Cela permet à toute combinaison de [[MyClass alloc] init] et de [MyClass sharedInstance] de renvoyer le même objet. [MyClass sharedInstance] serait simplement un peu plus efficace. Comment ça marche: [MyClass sharedInstance] appellera une fois [[MyClass alloc] init]. Un autre code pourrait aussi l'appeler, n'importe quel nombre de fois. Le premier appelant à init fera l'initialisation "normale" et stockera l'objet singleton à distance dans la méthode init. Tous les appels ultérieurs à init ignoreront complètement l'allocation retournée et renverront la même instance partagée. le résultat de alloc sera désalloué.
La méthode + sharedInstance fonctionnera comme toujours. Si ce n'est pas le premier appelant à appeler [[MyClass alloc] init], le résultat de init n'est pas le résultat de l'appel alloc, mais c'est OK.
Vous demandez si c'est le "meilleur moyen de créer singleton".
Quelques réflexions:
Premièrement, oui, il s'agit d'une solution thread-safe. Ce modèle dispatch_once
est le moyen moderne et sûr de générer des singletons dans Objective-C. Pas de soucis là-bas.
Vous avez cependant demandé si c'était la "meilleure" façon de le faire. Il faut cependant reconnaître que les instancetype
et [[self alloc] init]
sont potentiellement trompeurs s’ils sont utilisés conjointement avec des singletons.
Le bénéfice de instancetype
est qu’il s’agit d’une manière non équivoque de déclarer que la classe peut être sous-classée sans recourir à un type de id
, comme nous l’avions déjà fait dans le passé.
Mais le static
de cette méthode présente des problèmes de sous-classification. Et si ImageCache
et BlobCache
singletons étaient tous les deux des sous-classes d'une super classe Cache
sans implémenter leur propre méthode sharedCache
?
ImageCache *imageCache = [ImageCache sharedCache]; // fine
BlobCache *blobCache = [BlobCache sharedCache]; // error; this will return the aforementioned ImageCache!!!
Pour que cela fonctionne, vous devez vous assurer que les sous-classes implémentent leur propre méthode sharedInstance
(ou ce que vous appelez pour votre classe particulière).
En bout de ligne, votre sharedInstance
ressemble original supportera les sous-classes, mais ce ne sera pas le cas. Si vous avez l'intention de prendre en charge les sous-classes, incluez au minimum une documentation qui avertit les futurs développeurs qu'ils doivent remplacer cette méthode.
Pour une meilleure interopérabilité avec Swift, vous souhaiterez probablement que ce soit une propriété, pas une méthode de classe, par exemple:
@interface Foo : NSObject
@property (class, readonly, strong) Foo *sharedFoo;
@end
Vous pouvez ensuite écrire un getter pour cette propriété (l’implémentation utiliserait le modèle dispatch_once
que vous avez suggéré):
+ (Foo *)sharedFoo { ... }
L'avantage de ceci est que si un utilisateur Swift va l'utiliser, il ferait quelque chose comme:
let foo = Foo.shared
Notez qu'il n'y a pas de ()
, car nous l'avons implémenté en tant que propriété. À partir de Swift 3, voici comment accéder aux singletons. La définir comme une propriété facilite donc cette interopérabilité.
En passant, si vous regardez comment Apple définit leurs singletons, c'est le modèle qu'ils ont adopté, par exemple. leur singleton NSURLSession
est défini comme suit:
@property (class, readonly, strong) NSURLSession *sharedSession;
Le nom du singleton est un autre critère d'interopérabilité très mineur Swift. Il est préférable d'intégrer le nom du type plutôt que sharedInstance
. Par exemple, si la classe était Foo
, vous pouvez définir la propriété singleton comme sharedFoo
. Ou si la classe était DatabaseManager
, vous pouvez appeler la propriété sharedManager
. Ensuite, les utilisateurs Swift pourraient effectuer:
let foo = Foo.shared
let manager = DatabaseManager.shared
Clairement, si vous voulez vraiment utiliser sharedInstance
, vous pouvez toujours déclarer le nom Swift si vous voulez:
@property (class, readonly, strong) Foo* sharedInstance NS_Swift_NAME(shared);
Il est clair que lorsque nous écrivons du code Objective-C, nous ne devrions pas laisser l’interopérabilité Swift l’emporter sur d’autres considérations de conception. Néanmoins, si nous pouvons écrire du code prenant en charge les deux langages, c’est préférable.
Je suis d’accord avec d’autres qui soulignent que si vous voulez que ce soit un vrai singleton où les développeurs ne peuvent/ne devraient pas (accidentellement) instancier leurs propres instances, le qualificatif unavailable
de init
et new
est prudent.
Pour créer un singleton thread-safe, vous pouvez faire comme ceci:
@interface SomeManager : NSObject
+ (id)sharedManager;
@end
/* thread safe */
@implementation SomeManager
static id sharedManager = nil;
+ (void)initialize {
if (self == [SomeManager class]) {
sharedManager = [[self alloc] init];
}
}
+ (id)sharedManager {
return sharedManager;
}
@end
et ce blog explique très bien singleton singletons dans objc/cacoa
//Create Singleton
+( instancetype )defaultDBManager
{
static dispatch_once_t onceToken = 0;
__strong static id _sharedObject = nil;
dispatch_once(&onceToken, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
//In it method
-(instancetype)init
{
self = [super init];
if(self)
{
//Do your custom initialization
}
return self;
}