Crie singleton usando o dispatch_once do GCD em Objective-C

341

Se você pode segmentar o iOS 4.0 ou superior

Usando o GCD, é a melhor maneira de criar 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;
}
Ryan
fonte
2
Existe uma maneira de impedir que os usuários da classe chamem alocação / cópia?
Nicolas Miari 17/06/12
3
dispatch_once_t e dispatch_once parecem ter sido introduzido no 4.0, não 4.1 (ver: developer.apple.com/library/ios/#documentation/Performance/... )
Ben Flynn
11
Esse método se torna problemático se o init exigir o uso do objeto singleton. O código de Matt Gallagher funcionou para mim em mais de algumas ocasiões. cocoawithlove.com/2008/11/...
greg
11
Eu sei que é inconseqüente neste exemplo; mas por que as pessoas não usam mais 'novo'? dispatch_once (e uma vez, ^ {sharedInstance = [auto novo];} só olha que mais puro pouco É equivalente a alocação + inicialização..
Chris Hatton
3
Certifique-se de começar a usar o tipo de retorno instancetype. A conclusão do código é muito melhor ao usá-lo em vez de id.
Rogers

Respostas:

215

Essa é uma maneira perfeitamente aceitável e segura de thread para criar uma instância da sua classe. Tecnicamente, pode não ser um "singleton" (já que só pode haver um desses objetos), mas desde que você use apenas o [Foo sharedFoo]método para acessar o objeto, isso é bom o suficiente.

Dave DeLong
fonte
4
Como você o lança?
samvermette
65
@samvermette você não. o objetivo de um singleton é que ele sempre existirá. portanto, você não a libera e a memória é recuperada com a saída do processo.
Dave DeLong
6
@ Dave DeLong: Na minha opinião, o objetivo de ter singleton não é uma certeza de sua imortalidade, mas a certeza de que temos uma instância. E se esse singleton decrementar um semáforo? Você não pode simplesmente dizer arbitrariamente que sempre existirá.
precisa saber é o seguinte
4
@hooleyhoop Sim, na documentação . "Se chamada simultaneamente a partir de vários threads, esta função espera de forma síncrona até que o bloco seja concluído."
21413 Kevin
3
@ WalterMartinVargas-Pena referência forte é mantido pela variável estática
Dave DeLong
36

instancetype

instancetypeé apenas uma das muitas extensões de idioma para Objective-C, com mais sendo adicionadas a cada nova versão.

Conheça, ame.

E tome como exemplo de como prestar atenção aos detalhes de baixo nível pode fornecer insights sobre novas e poderosas maneiras de transformar o Objective-C.

Consulte aqui: 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;
}
Zelko
fonte
4
dica incrível, obrigado! instancetype é uma palavra-chave contextual que pode ser usada como um tipo de resultado para sinalizar que um método retorna um tipo de resultado relacionado. ... Com o tipo instancet, o compilador inferirá corretamente o tipo.
Fattie
11
Não está claro para mim o que os dois trechos significam aqui, eles são equivalentes entre si? Um é preferível ao outro? Seria bom se o autor pudesse adicionar um pouco de explicações para isso.
Galactica
33

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
Sergey Petruk
fonte
Como o init não está disponível? Não é pelo menos disponível para um init?
Mel
2
Singleton deve ter apenas um ponto de acesso. E este ponto é sharedInstance. Se tivermos o método init no arquivo * .h, você poderá criar outra instância singleton. Isso contradiz a definição de um singleton.
Sergey Petruk
11
@ asma22 __attribute __ ((não disponível ()) faz não disponível para uso desses métodos Se outro programador quer utilizar o método marcado como indisponível, ele recebe erro.
Sergey Petruk
11
Estou totalmente obter e estou feliz que eu aprendi algo novo, errado nada com a sua resposta, só pode ser um pouco confuso para iniciantes ...
Mel
11
Isso só funciona para MySingleton, por exemplo, MySingleton.meu estou chamando[super alloc]
Sergey Petruk
6

Você pode evitar que a classe seja alocada substituindo o método de atribuiçã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;
}
i-developer
fonte
11
Isso responde à minha pergunta nos comentários acima. Não que eu seja muito a favor de programação defensiva, mas ...
Nicolas Miari 14/11
5

Dave está correto, está perfeitamente bem. Você pode consultar os documentos da Apple sobre a criação de um singleton para obter dicas sobre como implementar 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.

cristão
fonte
8
eh ... esse não é o melhor exemplo de criação de um singleton. Não é necessário substituir os métodos de gerenciamento de memória.
Dave DeLong
19
Isso é completamente inválido usando o ARC.
logancautrell
O documento citado foi retirado. Além disso, respostas que são apenas links para conteúdo externo geralmente são respostas ruins para o SO. No mínimo, trechos relevantes da sua resposta. Não se preocupe aqui, a menos que você queira que a maneira antiga seja salva para a posteridade.
toolbear
4

Se você quer ter certeza de que [[MyClass aloc] init] retorna o mesmo objeto que sharedInstance (não é necessário na minha opinião, mas algumas pessoas querem isso), isso pode ser feito com muita facilidade e segurança usando um segundo 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;
}

Isso permite que qualquer combinação de [[MyClass alocado] init] e [MyClass sharedInstance] retorne o mesmo objeto; [MyClass sharedInstance] seria apenas um pouco mais eficiente. Como funciona: [MyClass sharedInstance] chamará [[MyClass shared] init] uma vez. Outro código poderia chamá-lo também, inúmeras vezes. O primeiro chamador a init fará a inicialização "normal" e armazenará o objeto singleton no método init. Quaisquer chamadas posteriores ao init ignorarão completamente o que a alocação retornou e retornará a mesma sharedInstance; o resultado da alocação será desalocado.

O método + sharedInstance funcionará como sempre. Se não for o primeiro chamador a ligar para [[MyClass assign] init], o resultado do init não será o resultado da chamada de alocação, mas tudo bem.

gnasher729
fonte
2

Você pergunta se esta é a "melhor maneira de criar singleton".

Algumas reflexões:

  1. Primeiro, sim, esta é uma solução segura para threads. Esse dispatch_oncepadrão é a maneira moderna e segura de thread de gerar singletons no Objective-C. Não se preocupe lá.

  2. Você perguntou, porém, se essa é a "melhor" maneira de fazer isso. Deve-se reconhecer, porém, que o instancetypee [[self alloc] init]é potencialmente enganoso quando usado em conjunto com singletons.

    O benefício instancetypedisso é que é uma maneira inequívoca de declarar que a classe pode ser subclassificada sem recorrer a um tipo de id, como tivemos que fazer no passado.

    Mas o staticmétodo apresenta desafios de subclassificação. E se ImageCachee BlobCachesingletons fossem subclasses de uma Cachesuperclasse sem implementar seu próprio sharedCachemétodo?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    Para que isso funcione, você deve garantir que as subclasses implementem seu próprio sharedInstancemétodo (ou o que você chamar para sua classe específica).

    Resumindo, seu original sharedInstance parece suportar subclasses, mas não. Se você pretende dar suporte à subclasse, inclua pelo menos a documentação que avisa os desenvolvedores futuros que eles devem substituir esse método.

  3. Para melhor interoperabilidade com o Swift, você provavelmente deseja definir isso como uma propriedade, não um método de classe, por exemplo:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    Em seguida, você pode prosseguir e escrever um getter para essa propriedade (a implementação usaria o dispatch_oncepadrão sugerido):

    + (Foo *)sharedFoo { ... }

    O benefício disso é que, se um usuário Swift for usá-lo, ele fará algo como:

    let foo = Foo.shared

    Observe que não existe (), porque nós o implementamos como uma propriedade. A partir do Swift 3, é assim que os singletons são geralmente acessados. Portanto, 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 NSURLSessionsingleton é definido da seguinte maneira:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Outra consideração muito pequena sobre a interoperabilidade Swift foi o nome do singleton. É melhor se você pode incorporar o nome do tipo em vez de sharedInstance. Por exemplo, se a classe era Foo, você pode definir a propriedade singleton como sharedFoo. Ou, se a classe fosse DatabaseManager, você poderia chamar a propriedade sharedManager. Os usuários do Swift poderiam fazer:

    let foo = Foo.shared
    let manager = DatabaseManager.shared

    Claramente, se você realmente deseja usar sharedInstance, sempre pode declarar o nome Swift, se desejar:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    Claramente, ao escrever o código Objective-C, não devemos permitir que a interoperabilidade Swift supere outras considerações de design, mas ainda assim, se pudermos escrever código que suporte graciosamente os dois idiomas, é preferível.

  5. Concordo com os outros que apontam que, se você quer que isso seja um verdadeiro singleton onde os desenvolvedores não pode / não deve (acidentalmente) instanciar suas próprias instâncias, o unavailablequalificador no inite newé prudente.

Roubar
fonte
0

Para criar um singleton seguro para threads, faça o seguinte:

@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 explica singleton muito bem singletons em objc / cacau

Hancock_Xu
fonte
você está vinculando a um artigo muito antigo, enquanto o OP solicita características sobre a implementação mais moderna.
Vikingosegundo
11
A questão é sobre uma implementação específica. Você acabou de publicar outra implementação. Lá, você nem tenta responder à pergunta.
vikingosegundo
11
@vikingosegundo O solicitante ask weather, o GCD é a melhor maneira de criar um singleton seguro para Thread, minha resposta dá outra opção.
Hancock_Xu
o solicitante pergunta se uma determinada implementação é segura para threads. ele não está pedindo opções.
vikingosegundo
0
//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;
}
Rohit Kashyap
fonte
0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
Nayab Muhammad
fonte