Ma méthode d'accès 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?
Une autre option consiste à utiliser la méthode +(void)initialize
. De la documentation:
Le moteur d'exécution envoie
initialize
à chaque classe d'un programme exactement une fois, juste avant que la classe, ou toute classe qui en hérite, reçoive 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 moteur d'exécution envoie le messageinitialize
aux classes d'une manière thread-safe. Les superclasses reçoivent ce message avant leurs sous-classes.
Donc, vous pouvez faire quelque chose qui ressemble à ceci:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
@end
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;
}
Puisque Kendall a posté un singleton threadsafe qui tente d’éviter le blocage des coûts, je pensais en lancer 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;
}
Ok, laissez-moi vous expliquer comment cela fonctionne:
Cas rapide: En exécution normale, sharedInstance
a déjà été défini. La boucle while
n'est donc jamais exécutée et la fonction revient après avoir simplement testé l'existence de la variable.
Cas lent: Si sharedInstance
n'existe pas, une instance est allouée et copiée dans celle-ci à l'aide d'un Compare And Swap ('CAS');
Cas concerné: Si deux threads tentent tous deux d'appeler sharedInstance
simultanément ETsharedInstance
n'existe pas à En même temps, ils initialiseront de nouvelles instances du singleton et tenteront de le placer en position CAS. Quel que soit le joueur qui gagne, les retours CAS immédiatement, celui qui perd, libère l'instance qu'il vient d'allouer et renvoie le (désormais défini) sharedInstance
. L'unique OSAtomicCompareAndSwapPtrBarrier
agit à la fois comme une barrière en écriture pour le thread de réglage et une barrière en lecture du thread de test.
static MyClass * sharedInst = nil; + (id) sharedInstance { @synchronize (self) { si (sharedInst == nil) { /* sharedInst configuré dans init */ [[self alloc] init]; } } return sharedInst; } - (id) init { if (sharedInst! = nil) { [ NSException levée: format NSInternalInconsistencyException : @ "[% @% @] Ne peut pas être appelé; utilisez + [% @% @] à la place"], NSStringFromClass ([self class]), NSStringFromSelector ( _cmd), NSStringFromClass ([self class]), NSStringFromSelector (@selector (sharedInstance) "]; } sinon if (self = [super init]) { sharedInst = self; /* Quelle que soit la classe spécifique ici */ } renvoyer sharedInst; } /* Celles-ci ne font probablement rien dans Une application du gouvernement du Canada. singleton en tant que singleton dans une application non CG */ - (NSUInteger) holdCount { return NSUIntegerMax ; } - (vide à sens unique) relâcher { } - (id) retenir { return sharedInst; } - (id) autorelease { return sharedInst; }
Edit: Cette implémentation est obsolète avec ARC. Veuillez regarder Comment puis-je implémenter un singleton Objective-C compatible avec ARC? pour une implémentation correcte
Toutes les implémentations d'initialize 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 par le biais de 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, ce qui entraînera un second 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, donnez à 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’affirmation; ou seulement par NSAssert.)
Une explication détaillée du code macro Singleton est disponible sur le blog Cocoa With Love
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html .
J'ai une variante intéressante sur sharedInstance qui est thread-safe, mais ne se verrouille pas après l'initialisation. Je ne suis pas encore assez sûr de pouvoir modifier la réponse en haut 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;
}
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 le dispatch/once.h pour comprendre ce qui se passe. Dans ce cas, les commentaires d'en-tête sont plus applicables que la documentation ou la page de manuel.
J'ai converti singleton dans une classe, afin que d'autres classes puissent hériter des propriétés de 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 de classe dans laquelle vous souhaitez devenir singleton.
#import "Singleton.h"
@interface SomeClass : Singleton {
}
@end
@implementation SomeClass
DEFINE_SHARED_INSTANCE;
@end
La seule limitation concernant la classe Singleton est qu'il s'agit d'une sous-classe NSObject. Mais la plupart du temps, j'utilise des singletons dans mon code, il s'agit en fait de sous-classes NSObject. Cette classe facilite donc vraiment ma vie et rend le code plus propre.
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;
}
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
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 donc les coûts de synchronisation après l'initialisation?
Pour une discussion approfondie du motif singleton dans Objective-C, regardez ici:
Voici une macro que j'ai mis ensemble:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
Il est basé sur le travail ici de Matt Gallagher Mais changer l'implémentation pour utiliser méthode swizzling comme décrit ici par Dave MacLachlan de Google .
Je me félicite des commentaires/contributions.
KLSingleton est:
- Subclassible (au nième degré)
- Compatible ARC
- Coffre-fort avec
alloc
etinit
- Chargé paresseusement
- Fil-safe
- Sans verrouillage (utilise + initialize, pas @synchronize)
- Sans macro
- Sans grésillement
- Facile
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 grandement le singleton.
Voici les macros que j'ai écrites à partir 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 alors qu'elle est presque vide? Cohérence du code entre les fichiers d’en-tête et de code; maintenabilité au cas où vous voudriez ajouter plus de méthodes automatiques ou la changer.
J'utilise la méthode d'initialisation pour créer le singleton tel qu'il est utilisé dans la réponse la plus populaire ici (au moment de l'écriture).
Avec les méthodes de classe Objective C, nous pouvons simplement éviter d'utiliser le motif singleton de la manière habituelle, à partir de:
[[Librarian sharedInstance] openLibrary]
à:
[Librarian openLibrary]
en encapsulant la classe dans une autre classe qui a juste 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 pas exemple!
J'ai écrit un blog plus détaillé ici :)
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;
}
Vous ne voulez pas vous synchroniser sur vous-même ... Puisque l'objet de soi n'existe pas encore! Vous finissez par verrouiller une valeur d'identifiant 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 synchroniser sur l'objet de classe:
@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
Mon chemin 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 (! Initialized) consiste à s'assurer qu'il n'est pas encore initialisé lorsque le thread en cours acquiert le verrou.
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
Je voulais juste laisser ça ici pour que je ne le perde pas. 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;
}
Je n'ai pas lu toutes les solutions, alors pardonnez-moi si ce code est redondant.
Ceci est la mise en œuvre la plus sécuritaire à mon avis.
+(SingletonObject *) sharedManager
{
static SingletonObject * sharedResourcesObj = nil;
@synchronized(self)
{
if (!sharedResourcesObj)
{
sharedResourcesObj = [[SingletonObject alloc] init];
}
}
return sharedResourcesObj;
}