ios - Crea singleton usando dispatch_once de GCD en Objective C
objective-c grand-central-dispatch (9)
tipo de instancia
instancetype
es solo una de las muchas extensiones de lenguaje para Objective-C
, y se agregan más con cada nueva versión.
Lo sabes, me encanta.
Y tómelo como un ejemplo de cómo prestar atención a los detalles de bajo nivel puede brindarle información sobre nuevas y poderosas formas de transformar Objective-C.
+ (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;
}
Si puedes apuntar a iOS 4.0 o superior
Usando GCD, ¿es la mejor manera de crear singleton en Objective C (seguro para subprocesos)?
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Dave tiene razón, eso está perfectamente bien. Si lo desea, puede consultar la documentación de Apple sobre cómo crear un singleton para obtener consejos sobre cómo implementar algunos de los otros métodos para asegurarse de que solo se pueda crear uno si las clases eligen NO usar el método sharedFoo.
Esta es una forma perfectamente aceptable y segura para crear una instancia de su clase. Técnicamente, puede que no sea un "singleton" (en el sentido de que solo puede haber uno de estos objetos), pero siempre y cuando solo uses el método [Foo sharedFoo]
para acceder al objeto, esto es suficiente.
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
Para crear hilo seguro singleton puede hacer esto:
@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
y este blog explica singleton muy bien singletons en objc / cocoa
Preguntas si esta es la "mejor forma de crear singleton".
Unos pocos pensamientos:
Primero, sí, esta es una solución segura para subprocesos. Este patrón
dispatch_once
es la forma moderna y segura de generar subprocesos en Objective-C. No te preocupes allí.Sin embargo, usted preguntó si esta es la "mejor" forma de hacerlo. Sin embargo, se debe reconocer que el tipo de
instancetype
y[[self alloc] init]
pueden ser engañosos cuando se usan junto con singletons.El beneficio de
instancetype
es que es una forma inequívoca de declarar que la clase puede ser subclasificada sin tener que recurrir a un tipo deid
, como tuvimos que hacer en el pasado.Pero la
static
en este método presenta desafíos de subclasificación. ¿QuéImageCache
si los singletonsImageCache
yBlobCache
fueran subclases de una superclaseCache
sin implementar su propio métodosharedCache
?ImageCache *imageCache = [ImageCache sharedCache]; // fine BlobCache *blobCache = [BlobCache sharedCache]; // error; this will return the aforementioned ImageCache!!!
Para que esto funcione, deberías asegurarte de que las subclases implementen su propio
sharedInstance
(o como se llame para tu clase en particular).En
sharedInstance
parece que susharedInstance
original desharedInstance
admitirá subclases, pero no lo hará. Si tiene la intención de admitir subclases, al menos incluya documentación que advierta a los futuros desarrolladores que deben anular este método.Para una mejor interoperabilidad con Swift, probablemente desee definir esto como una propiedad, no un método de clase, por ejemplo:
@interface Foo : NSObject @property (class, readonly, strong) Foo *sharedFoo; @end
Luego puede seguir adelante y escribir un captador para esta propiedad (la implementación usaría el patrón
dispatch_once
que sugirió):+ (Foo *)sharedFoo { ... }
El beneficio de esto es que si un usuario de Swift va a usarlo, haría algo como:
let foo = Foo.shared
Tenga en cuenta que no hay
()
, porque lo implementamos como una propiedad. Comenzando Swift 3, así es como generalmente se accede a los singletons. Así que definirlo como una propiedad ayuda a facilitar esa interoperabilidad.Además, si observa cómo Apple está definiendo sus singletons, este es el patrón que han adoptado, por ejemplo, su singleton
NSURLSession
se define de la siguiente manera:@property (class, readonly, strong) NSURLSession *sharedSession;
Otra consideración de interoperabilidad Swift muy menor fue el nombre del singleton. Es mejor si puedes incorporar el nombre del tipo, en lugar de la
sharedInstance
. Por ejemplo, si la clase eraFoo
, podría definir la propiedad singleton comosharedFoo
. O si la clase eraDatabaseManager
, puede llamar a la propiedadsharedManager
. Entonces los usuarios de Swift podrían hacer:let foo = Foo.shared let manager = DatabaseManager.shared
Claramente, si realmente desea utilizar la
sharedInstance
, siempre puede declarar el nombre Swift si desea:@property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);
Claramente, al escribir el código de Objective-C, no debemos permitir que la interoperabilidad de Swift supere otras consideraciones de diseño, pero aún así, si podemos escribir código que sea compatible con ambos idiomas, es preferible.
Estoy de acuerdo con otros que señalan que si desea que esto sea un verdadero singleton donde los desarrolladores no pueden / no deberían (accidentalmente) crear instancias de sus propias instancias, el calificador
unavailable
eninit
ynew
es prudente.
Puede evitar que la clase se asigne sobrescribiendo el método de asignación.
@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;
}
Si desea asegurarse de que [[MyClass alloc] init] devuelva el mismo objeto que sharedInstance (no es necesario en mi opinión, pero algunas personas lo desean), eso se puede hacer de manera muy fácil y segura utilizando una segunda 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;
}
Esto permite que cualquier combinación de [[MyClass alloc] init] y [MyClass sharedInstance] devuelva el mismo objeto; [MyClass sharedInstance] sería un poco más eficiente. Cómo funciona: [MyClass sharedInstance] llamará a [[alloc MyClass] init] una vez. Otro código podría llamarlo también, cualquier número de veces. La primera persona que llama a init realizará la inicialización "normal" y almacenará el objeto singleton en el método init. Cualquier llamada posterior a init ignorará completamente la asignación devuelta y devolverá la misma SharedInstance; El resultado de la asignación será desasignado.
El método + sharedInstance funcionará como siempre lo hizo. Si no es la primera persona que llama a llamar a [[alloc de MyClass] init], entonces el resultado de init no es el resultado de la llamada de asignación, pero está bien.
//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;
}