ios - Criar um singleton usando o dispatch_once do GCD no Objective C
objective-c grand-central-dispatch (6)
instancetype
instancetype
é apenas uma das muitas extensões de linguagem para Objective-C
, com mais sendo adicionadas a cada nova versão.
Saiba disso, ame-o.
E tome-o como um exemplo de como prestar atenção aos detalhes de baixo nível pode dar-lhe insights sobre maneiras novas e poderosas de transformar o 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;
}
Se você pode segmentar o iOS 4.0 ou superior
Usando o GCD, é a melhor maneira de criar um singleton no Objective C (thread safe)?
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Dave está correto, isso está perfeitamente bem. Você pode verificar os documentos da Apple sobre como criar um singleton para obter dicas sobre a implementação de alguns dos outros métodos para garantir que apenas um possa ser criado se as classes optarem por NÃO usar o método sharedFoo.
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 criar um singleton seguro para threads, você pode fazer assim:
@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
e este blog explicam singleton muito bem singletons em objc / cacau
Você pergunta se esta é a "melhor maneira de criar singleton".
Alguns pensamentos:
Primeiro, sim, esta é uma solução segura para threads. Esse padrão
dispatch_once
é a maneira moderna e segura de gerar singletons em Objective-C. Não se preocupe lá.Você perguntou, no entanto, se essa é a melhor maneira de fazer isso. Deve-se reconhecer, no entanto, que o
instancetype
e[[self alloc] init]
é potencialmente enganador quando usado em conjunto com singletons.O benefício do
instancetype
é que é uma maneira inequívoca de declarar que a classe pode ser subclassificada sem recorrer a um tipo deid
, como tivemos que fazer no passado.Mas a
static
nesse método apresenta desafios de subclasses. E se os singletonsBlobCache
eBlobCache
fossem subclasses de uma superclasseCache
sem implementar seu próprio métodosharedCache
?ImageCache *imageCache = [ImageCache sharedCache]; // fine BlobCache *blobCache = [BlobCache sharedCache]; // error; this will return the aforementioned ImageCache!!!
Para que isso funcione, você precisa ter certeza de que as subclasses implementam seu próprio
sharedInstance
(ou o que você chama para sua classe particular).Bottom line, seu
sharedInstance
original parece que vai suportar subclasses, mas não vai. Se você pretende suportar subclasses, inclua, no mínimo, documentação que avise os futuros desenvolvedores de que eles devem substituir esse método.Para melhor interoperabilidade com o Swift, você provavelmente vai querer definir isso como uma propriedade, não um método de classe, por exemplo:
@interface Foo : NSObject @property (class, readonly, strong) Foo *sharedFoo; @end
Então você pode ir em frente e escrever um getter para essa propriedade (a implementação usaria o padrão
dispatch_once
você sugeriu):+ (Foo *)sharedFoo { ... }
O benefício disso é que, se um usuário do Swift for usá-lo, ele fará algo como:
let foo = Foo.shared
Note que não há
()
, porque nós o implementamos como uma propriedade. Iniciando o Swift 3, é assim que os singletons são geralmente acessados. Então defini-lo como uma propriedade ajuda a facilitar essa interoperabilidade.Como um aparte, se você observar como a Apple está definindo seus singletons, esse é o padrão que eles adotaram, por exemplo, seu singleton
NSURLSession
é definido da seguinte forma:@property (class, readonly, strong) NSURLSession *sharedSession;
Outra consideração de interoperabilidade Swift muito menor era o nome do singleton. É melhor se você puder incorporar o nome do tipo, em vez de
sharedInstance
. Por exemplo, se a classe forFoo
, você poderá definir a propriedade singleton comosharedFoo
. Ou, se a classe forDatabaseManager
, você poderá chamar a propriedadesharedManager
. Então os usuários do Swift poderiam fazer:let foo = Foo.shared let manager = DatabaseManager.shared
Claramente, se você realmente quer usar
sharedInstance
, você sempre pode declarar o nome Swift se você quiser:@property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);
Claramente, ao escrever o código Objective-C, não devemos deixar que a interoperabilidade do Swift supere outras considerações de design, mas ainda assim, se pudermos escrever código que suporte ambas as linguagens, é preferível.
Eu concordo com outras pessoas que apontam que, se você quer que isso seja um verdadeiro singleton, onde os desenvolvedores não podem / não devem (acidentalmente) instanciar suas próprias instâncias, o qualificador
unavailable
noinit
e nonew
é prudente.
Você pode evitar que a classe seja alocada sobrescrevendo o método de alocação.
@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;
}