¿Cómo debería ser mi Singleton Objective-C? [cerrado]
-
02-07-2019 - |
Pregunta
Mi método de acceso singleton suele ser una variante de:
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
return(gInstance);
}
¿Qué podría estar haciendo para mejorar esto?
Solución
Otra opción es usar el método + (void) initialize
. De la documentación:
El tiempo de ejecución envía
initialize
a cada clase en un programa exactamente una vez justo antes de que la clase, o cualquier clase que hereda de ella, reciba su primer mensaje desde dentro del programa. (Por lo tanto, el método nunca se puede invocar si no se usa la clase). El tiempo de ejecución envía el mensajeinitialize
a las clases de una manera segura para subprocesos. Las superclases reciben este mensaje antes de sus subclases.
Así que podrías hacer algo parecido a esto:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
Otros consejos
@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
@end
Por mi otra respuesta a continuación, creo que deberías estar haciendo:
+ (id)sharedFoo
{
static dispatch_once_t once;
static MyFoo *sharedFoo;
dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
return sharedFoo;
}
Desde Kendall publicó un mensaje seguro Singleton que intenta evitar los costos de bloqueo, pensé que también lanzaría uno:
#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;
}
Bien, déjame explicarte cómo funciona esto:
-
Caso rápido: en la ejecución normal,
sharedInstance
ya se ha establecido, por lo que elwhile
nunca se ejecuta y la función regresa después de simplemente probar la existencia de la variable ; -
Caso lento: si
sharedInstance
no existe, entonces se asigna una instancia y se copia en ella utilizando un Compare And Swap ('CAS'); -
Caso contendido: si dos subprocesos intentan llamar
sharedInstance
al mismo tiempo, ANDsharedInstance
no existe en el al mismo tiempo, ambos inicializarán las nuevas instancias del singleton e intentarán colocarlo en su posición. Cualquiera que gane el CAS devuelve inmediatamente, el que pierde libera la instancia que acaba de asignar y devuelve el (código ahora compartido)sharedInstance
. El únicoOSAtomicCompareAndSwapPtrBarrier
actúa como una barrera de escritura para el hilo de configuración y una barrera de lectura del hilo de prueba.
static MyClass *sharedInst = nil; + (id)sharedInstance { @synchronize( self ) { if ( sharedInst == nil ) { /* sharedInst set up in init */ [[self alloc] init]; } } return sharedInst; } - (id)init { if ( sharedInst != nil ) { [NSException raise:NSInternalInconsistencyException format:@"[%@ %@] cannot be called; use +[%@ %@] instead"], NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class]), NSStringFromSelector(@selector(sharedInstance)"]; } else if ( self = [super init] ) { sharedInst = self; /* Whatever class specific here */ } return sharedInst; } /* These probably do nothing in a GC app. Keeps singleton as an actual singleton in a non CG app */ - (NSUInteger)retainCount { return NSUIntegerMax; } - (oneway void)release { } - (id)retain { return sharedInst; } - (id)autorelease { return sharedInst; }
Editar: Esta implementación está obsoleta con ARC. Por favor, eche un vistazo a Cómo ¿Implemento un Singleton de Objective-C que sea compatible con ARC? para la implementación correcta.
Todas las implementaciones de inicialización que he leído en otras respuestas comparten un error común.
+ (void) initialize {
_instance = [[MySingletonClass alloc] init] // <----- Wrong!
}
+ (void) initialize {
if (self == [MySingletonClass class]){ // <----- Correct!
_instance = [[MySingletonClass alloc] init]
}
}
La documentación de Apple recomienda que marque el tipo de clase en su bloque de inicialización. Porque las subclases llaman a la inicialización por defecto. Existe un caso no obvio donde las subclases pueden crearse indirectamente a través de KVO. Por si agrega la siguiente línea en otra clase:
[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Objective-C creará implícitamente una subclase de MySingletonClass que resultará en una segunda activación de + initialize
.
Puede pensar que debería verificar implícitamente la inicialización duplicada en su bloque de inicio como tal:
- (id) init { <----- Wrong!
if (_instance != nil) {
// Some hack
}
else {
// Do stuff
}
return self;
}
Pero te dispararás en el pie; o peor, dale a otro desarrollador la oportunidad de dispararse en el pie.
- (id) init { <----- Correct!
NSAssert(_instance == nil, @"Duplication initialization of singleton");
self = [super init];
if (self){
// Do stuff
}
return self;
}
TL; DR, aquí está mi implementación
@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
(Reemplace ZAssert con nuestra propia macro de aserción; o simplemente NSAssert.)
Una explicación detallada del código de macro de Singleton se encuentra en el blog Cocoa With Love
http://cocoawithlove.com/2008/11 /singletons-appdelegates-and-top-level.html .
Tengo una variación interesante en sharedInstance que es seguro para subprocesos, pero no se bloquea después de la inicialización. Todavía no estoy lo suficientemente seguro como para modificar la respuesta principal según lo solicitado, pero lo presento para una discusión más profunda:
// 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;
}
Respuesta corta: Fabuloso.
Respuesta larga: algo así como ...
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
Asegúrese de leer el encabezado de dispatch / once.h para entender que esta pasando. En este caso, los comentarios del encabezado son más aplicables que los documentos o la página del manual.
He incluido singleton en una clase, por lo que otras clases pueden heredar propiedades 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
Y aquí hay un ejemplo de alguna clase, que quieres convertirte en singleton.
#import "Singleton.h"
@interface SomeClass : Singleton {
}
@end
@implementation SomeClass
DEFINE_SHARED_INSTANCE;
@end
La única limitación sobre la clase Singleton, es que es una subclase NSObject. Pero la mayoría del tiempo que uso singletons en mi código, en realidad son subclases de NSObject, por lo que esta clase realmente me facilita la vida y hace que el código sea más limpio.
Esto también funciona en un entorno no recolectado como basura.
@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
¿No debería ser seguro para los subprocesos y evitar el costoso cuidado de la primera llamada?
+ (MySingleton*)sharedInstance
{
if (sharedInstance == nil) {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MySingleton alloc] init];
}
}
}
return (MySingleton *)sharedInstance;
}
Aquí hay una macro que reuní:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
Se basa en el trabajo aquí por Matt Gallagher Pero cambiando la implementación para usar el método como lo describe Dave MacLachlan de Google .
Doy la bienvenida a los comentarios / contribuciones.
¿Qué tal
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
if (gInstance == NULL) {
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
}
return(gInstance);
}
¿Entonces evita el costo de sincronización después de la inicialización?
Para una discusión en profundidad del patrón de singleton en Objective-C, mira aquí:
KLSingleton es:
- Subclasible (en el grado n-th)
- compatible con ARC
- Seguro con
alloc
yinit
- cargado perezosamente
- Hilo seguro
- Sin bloqueo (usa + inicializa, no @synchronize)
- Macro-free
- Swizzle-free
- Simple
No quieres sincronizar en ti mismo ... ¡Ya que el objeto self no existe todavía! Terminas bloqueando un valor de id temporal. Desea asegurarse de que nadie más pueda ejecutar métodos de clase (sharedInstance, alloc, allocWithZone :, etc), por lo que debe sincronizar en el objeto de la clase en su lugar:
@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
Solo quería dejar esto aquí para que no lo pierda. La ventaja de este es que se puede usar en InterfaceBuilder, que es una GRAN ventaja. Esto está tomado de otra pregunta que hice :
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;
}
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
Sé que hay muchos comentarios sobre esta "pregunta", pero no veo que mucha gente sugiera utilizar una macro para definir el singleton. Es un patrón tan común y una macro simplifica enormemente el singleton.
Aquí están las macros que escribí basadas en varias implementaciones de Objc que he visto.
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; \
}
Ejemplo de uso:
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
¿Por qué una macro de interfaz cuando está casi vacía? Coherencia de código entre el encabezado y los archivos de código; mantenibilidad en caso de que desee agregar más métodos automáticos o cambiarlo.
Estoy usando el método de inicialización para crear el singleton como se usa en la respuesta más popular aquí (al momento de escribir).
Con los métodos de clase de Objective C, podemos evitar el uso del patrón singleton de la manera habitual, desde:
[[Librarian sharedInstance] openLibrary]
a:
[Librarian openLibrary]
envolviendo la clase dentro de otra clase que solo tiene Métodos de clase , de esa manera no hay posibilidad de crear accidentalmente instancias duplicadas, ¡ya que no estamos creando ninguna instancia!
Escribí un blog más detallado aquí :)
Para ampliar el ejemplo 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;
}
Mi camino es simple como este:
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 el singleton ya está inicializado, el bloque LOCK no se ingresará. La segunda verificación si (! Initialized) es para asegurarse de que aún no se haya inicializado cuando el subproceso actual adquiera el LOCK.
No he leído todas las soluciones, así que perdona si este código es redundante.
Esta es la implementación más segura para subprocesos en mi opinión.
+(SingletonObject *) sharedManager
{
static SingletonObject * sharedResourcesObj = nil;
@synchronized(self)
{
if (!sharedResourcesObj)
{
sharedResourcesObj = [[SingletonObject alloc] init];
}
}
return sharedResourcesObj;
}
Por lo general uso un código más o menos similar al de la respuesta de Ben Hoffstein (que también saqué de Wikipedia). Lo uso por las razones expuestas por Chris Hanson en su comentario.
Sin embargo, a veces tengo la necesidad de colocar un singleton en un NIB, y en ese caso uso lo siguiente:
@implementation Singleton
static Singleton *singleton = nil;
- (id)init {
static BOOL initialized = NO;
if (!initialized) {
self = [super init];
singleton = self;
initialized = YES;
}
return self;
}
+ (id)allocWithZone:(NSZone*)zone {
@synchronized (self) {
if (!singleton)
singleton = [super allocWithZone:zone];
}
return singleton;
}
+ (Singleton*)sharedSingleton {
if (!singleton)
[[Singleton alloc] init];
return singleton;
}
@end
Dejo la implementación de -retain
(etc.) al lector, aunque el código anterior es todo lo que necesita en un entorno de recolección de basura.
La respuesta aceptada, aunque compila, es incorrecta.
+ (MySingleton*)sharedInstance
{
@synchronized(self) <-------- self does not exist at class scope
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
Por documentación de Apple:
... Puedes adoptar un enfoque similar para sincronizar los métodos de clase de la clase asociada, utilizando el objeto Class en lugar de self.
Incluso si el uso de self funciona, no debería y esto me parece un error de copiar y pegar. La implementación correcta para un método de fábrica de clase sería:
+ (MySingleton*)getInstance
{
@synchronized([MySingleton class])
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}