Como deve ser meu singleton Objective-C? [fechadas]


334

Meu método acessador singleton geralmente é uma variante de:

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

O que eu poderia estar fazendo para melhorar isso?


27
O que você tem é bom, embora você possa mover a declaração da variável global para o método da instância + (o único local em que ela precisa ser usada, a menos que você permita que seja definida também) e usar um nome como + defaultMyClass ou + sharedMyClass para o seu método. + instância não é reveladora de intenção.
Chris Hanson

Como é improvável que a 'resposta' a essa pergunta mude em breve, estou colocando um bloqueio histórico na pergunta. Duas razões 1) Várias visualizações, votos e bom conteúdo 2) Para impedir o ioiô de abrir / fechar. Foi uma ótima pergunta para a época, mas perguntas desse tipo não são apropriadas para o estouro de pilha. Agora temos a Revisão de código para verificar o código de funcionamento. Leve toda a discussão desta questão para esta meta questão .
precisa

Respostas:


207

Outra opção é usar o +(void)initializemétodo A partir da documentação:

O tempo de execução envia initializepara cada classe em um programa exatamente uma vez antes da classe, ou qualquer classe que herda dela, recebe sua primeira mensagem de dentro do programa. (Portanto, o método nunca pode ser chamado se a classe não for usada.) O tempo de execução envia a initializemensagem para as classes de maneira segura para threads. As superclasses recebem essa mensagem antes de suas subclasses.

Então você pode fazer algo parecido com isso:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

7
Se o tempo de execução chamar isso apenas uma vez, o que o BOOL faz? Isso é uma precaução no caso de alguém chamar essa função explicitamente de seu código?
Aftermathew

5
Sim, é uma precaução, pois a função também pode ser chamada diretamente.
Robbie Hanson

33
Isso também é necessário porque pode haver subclasses. Se eles não substituirem +initializesuas superclasses, a implementação será chamada se a subclasse for usada pela primeira vez.
Sven

3
@Paul, você pode substituir o releasemétodo e torná-lo vazio. :)

4
@aryaxt: A partir dos documentos listados, isso já é seguro para threads. Portanto, a chamada é uma vez por tempo de execução. Essa parece ser a solução correta, segura para threads e otimizada de maneira eficiente.
lilbyrdie

95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[Fonte]


7
Isso é tudo que você normalmente deve usar para singletons. Entre outras coisas, manter suas classes instanciadas separadamente instantaneamente facilita o teste, porque você pode testar instâncias separadas em vez de ter uma maneira de redefinir seu estado.
31720 Chris Hanson

3
Stig Brautaset: Não, não há problema em deixar de fora os @ sincronizados neste exemplo. Ele está lá para lidar com a possível condição de corrida de dois threads executando esta função estática ao mesmo tempo, ambos passando pelo teste "if (! SharedSingleton)" ao mesmo tempo e resultando em dois [alvos de MySingleton] s. .. O @synchronized {scope block} força esse segundo segmento hipotético a aguardar o primeiro thread sair do {scope scope} antes de poder continuar nele. Eu espero que isso ajude! =)
MechEthan

3
O que impede alguém de ainda criar sua própria instância do objeto? MySingleton *s = [[MySingelton alloc] init];
Lindon fox 28/10/11

11
@lindonfox Qual é a resposta para sua pergunta?
Raffi Khatchadourian

11
@ Raffi - desculpe, acho que devo ter esquecido de colar na minha resposta. Enfim, eu peguei o livro Pro Objective-C Design Patterns for iOSe ele explica como você faz um singelton "estrito". Basicamente, como você não pode tornar privados os métodos de inicialização, é necessário substituir os métodos de alocação e cópia. Portanto, se você tentar fazer algo como [[MySingelton alloc] init]você, receberá um erro em tempo de execução (embora não seja um erro em tempo de compilação, infelizmente). Eu não entendo como todos os detalhes da criação do objeto, mas você implementar + (id) allocWithZone:(NSZone *)zoneo que é chamado emsharedSingleton
lindon fox

59

Pela minha outra resposta abaixo, acho que você deveria estar fazendo:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}

6
Não se preocupe com tudo o que você está fazendo acima. Faça seus (espero que extremamente poucos) singletons separadamente instanciados e tenha apenas um método compartilhado / padrão. O que você fez é necessário apenas se você realmente, realmente, quer SOMENTE uma única instância da sua classe. O que você não sabe, esp. para testes de unidade.
Chris Hanson

O problema é que este é o código de exemplo da Apple para "criar um singleton". Mas sim, você está absolutamente certo.
Colin Barrett

11
O código de amostra da Apple está correto se você deseja um singleton "verdadeiro" (ou seja, um objeto que só pode ser instanciado uma vez, sempre), mas como Chris diz, isso raramente é o que você deseja ou precisa, enquanto algum tipo de instância compartilhada configurável é o que você deseja. geralmente quer.
27630 Luke Redpath

Aqui está uma macro para o método acima: gist.github.com/1057420 . É isso que eu uso.
Kobski

11
Testes de unidade à parte, não há nada falando contra esta solução, correto? E é rápido e seguro.
LearnCocos2D

58

Como Kendall postou um singleton thread-safe que tenta evitar custos de bloqueio, pensei em lançar um também:

#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, deixe-me explicar como isso funciona:

  1. Caso rápido: na execução normal sharedInstancejá foi definida, o whileloop nunca é executado e a função retorna após simplesmente testar a existência da variável;

  2. Caso lento: se sharedInstancenão existir, uma instância é alocada e copiada para ela usando um Compare And Swap ('CAS');

  3. Caso contencioso: se dois encadeamentos tentarem chamar sharedInstanceao mesmo tempo E sharedInstance não existirem ao mesmo tempo, inicializarão novas instâncias do singleton e tentarão colocá-lo na posição CAS. Qualquer que seja o ganhador, o CAS retornará imediatamente, o que perder, libera a instância que acabou de alocar e retorna o (agora definido) sharedInstance. O single OSAtomicCompareAndSwapPtrBarrieratua como uma barreira de gravação para o encadeamento de configuração e uma barreira de leitura do encadeamento de teste.


18
Isso é um exagero total pela maior parte do tempo que pode ocorrer durante a vida útil de um aplicativo. No entanto, é correto no local, e a técnica de comparar e trocar é uma ferramenta útil para se conhecer, portanto +1.
21310 Steve Madsen

Boa resposta - a família OSAtomic é uma coisa boa a se saber #
Bill

11
@ Louis: Resposta incrível e realmente esclarecedora! Porém, uma pergunta: o que meu initmétodo deve fazer em sua abordagem? Lançar uma exceção quando sharedInstanceé inicializado não é uma boa ideia, acredito. O que fazer então para impedir que o usuário ligue initdiretamente muitas vezes?
matm

2
Geralmente não o impeço. Muitas vezes, existem razões válidas para permitir que o que é geralmente um singleton se multiplique instanciado, o mais comum é para certos tipos de teste de unidade. Se eu realmente quisesse impor uma única instância, provavelmente o método init verificaria se o global existia e, se existisse, liberaria o self e retornaria o global.
Louis Gerbarg 02/09/11

11
@ Tony atrasou a resposta, mas OSAtomicCompareAndSwapPtrBarrier exige uma volatilidade. Talvez a palavra-chave volátil seja impedir o compilador de otimizar a verificação. Veja: stackoverflow.com/a/5334727/449161 e developer.apple.com/library/mac/#documentation/Darwin/Reference/...
Ben Flynn

14
MyClass estático * sharedInst = nil;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == nil) {
            / * sharedInst configurado no init * /
            [[auto-alocação] init];
        }
    }
    return sharedInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [Aumento de NSException: NSInternalInconsistencyException
            formato: @ "[% @% @] não pode ser chamado; use + [% @% @]]",
            NSStringFromClass ([classe própria]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([auto-classe]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = self;
        / * Qualquer classe específica aqui * /
    }
    return sharedInst;
}

/ * Provavelmente não fazem nada
   um aplicativo de GC. Mantém singleton
   como um singleton real em um
   aplicativo não CG
* /
- (NSUInteger) reterContagem
{
    return NSUIntegerMax;
}

- liberação (vazia)
{
}

- (id) reter
{
    return sharedInst;
}

- (id) autorelease
{
    return sharedInst;
}

3
Notei que o clang reclama de um vazamento se você não atribuir o resultado [[self alloc] init]ao sharedInst.
Pix0r 6/05/09

Subverter o init como este é uma abordagem IMO bastante feia. Não mexa no init e / ou na criação real do objeto. Se você optar por um ponto de acesso controlado a uma instância compartilhada, enquanto não estiver inserindo singleton no objeto, terá um tempo mais feliz se estiver escrevendo testes etc. Singletons rígidos são muito usados ​​em demasia.
Occulus

12

Edit: Esta implementação obsoleta com o ARC. Veja como implementar um singleton Objective-C que seja compatível com o ARC? para implementação correta.

Todas as implementações de inicialização que eu li em outras respostas compartilham um erro comum.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

A documentação da Apple recomenda que você verifique o tipo de classe no seu bloco de inicialização. Porque as subclasses chamam a inicialização por padrão. Existe um caso não óbvio em que subclasses podem ser criadas indiretamente através do KVO. Pois se você adicionar a seguinte linha em outra classe:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

O Objective-C criará implicitamente uma subclasse de MySingletonClass, resultando em um segundo disparo de +initialize.

Você pode pensar que deve verificar implicitamente a inicialização duplicada no seu bloco init, como tal:

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

Mas você vai dar um tiro no próprio pé; ou pior, dê a outro desenvolvedor a oportunidade de dar um tiro no próprio pé.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL; DR, aqui está minha implementação

@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

(Substitua o ZAssert por nossa própria macro de asserção; ou apenas NSAssert.)


11
Eu viveria mais simples e evitaria inicializar por completo.
Tom Andersen


9

Eu tenho uma variação interessante no sharedInstance que é seguro para threads, mas não é bloqueado após a inicialização. Ainda não tenho certeza o suficiente para modificar a resposta principal, conforme solicitado, mas apresento para discussão adicional:

// 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;
}

11
+1 que é realmente intrigante. Eu poderia usar class_replaceMethodpara se transformar sharedInstanceem um clone de simpleSharedInstance. Dessa forma, você nunca precisaria se preocupar em adquirir uma @synchronizedfechadura novamente.
Dave DeLong

É o mesmo efeito, usar exchangeImplementations significa que, após o init, quando você chama sharedInstance, você está realmente chamando simpleSharedInstance. Na verdade, eu comecei com replaceMethod, mas decidiu que era melhor apenas mudar as implementações ao redor de modo que o original ainda existe, se necessário ...
Kendall Helmstetter Gelner

Em outros testes, não consegui que o replaceMethod funcionasse - em chamadas repetidas, o código ainda chamava o sharedInstance original em vez de simpleSharedInstance. Eu acho que pode ser porque ambos são métodos de nível de classe ... A substituição que usei foi: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); e algumas variações dos mesmos. Posso verificar o código que publiquei obras e simpleSharedInstance é chamado após a primeira passagem pelo sharedInstance.
Kendall Helmstetter Gelner,

Você pode criar uma versão segura do encadeamento que não pague os custos de bloqueio após a inicialização sem fazer muito trabalho de escassez de tempo de execução. Postei uma implementação abaixo.
Louis Gerbarg 15/03

11
+1 ótima ideia. Eu simplesmente amo as coisas que podemos fazer com o tempo de execução. Mas, na maioria dos casos, isso provavelmente é uma otimização prematura. Se eu realmente tivesse que me livrar do custo da sincronização, provavelmente usaria a versão sem bloqueio da Louis.
Sven

6

Resposta curta: Fabuloso.

Resposta longa: Algo 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

Leia o cabeçalho dispatch / once.h para entender o que está acontecendo. Nesse caso, os comentários do cabeçalho são mais aplicáveis ​​que os documentos ou a página de manual.


5

Eu rolei singleton em uma classe, para que outras classes possam herdar propriedades 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

E aqui está um exemplo de alguma classe, na qual você deseja se tornar único.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

A única limitação sobre a classe Singleton é que ela é a subclasse NSObject. Mas na maioria das vezes eu uso singletons no meu código, eles são de fato subclasses NSObject, então essa classe realmente facilita minha vida e torna o código mais limpo.


Você pode querer usar algum outro mecanismo de bloqueio, porque @synchronizedé terrivelmente lento e deve ser evitado.
precisa saber é o seguinte

2

Isso funciona em um ambiente sem coleta de lixo também.

@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

2

Isso não deve ser seguro e evitar o bloqueio caro após a primeira chamada?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}

2
A técnica de bloqueio com verificação dupla usada aqui geralmente é um problema real em alguns ambientes (consulte aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf ou no Google). Até que seja mostrado o contrário, eu suporia que o Objective-C não é imune. Consulte também wincent.com/a/knowledge-base/archives/2006/01/… .
21310 Steve Madsen


2

E se

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

Então você evita o custo da sincronização após a inicialização?


Veja discussões sobre o bloqueio com verificação dupla em outras respostas.
i_am_jorf


1

KLSingleton é:

  1. Subclassível (até o n-ésimo grau)
  2. Compatível com ARC
  3. Seguro com alloceinit
  4. Carregado preguiçosamente
  5. Discussão segura
  6. Sem bloqueio (usa + inicializar, não @ sincronizar)
  7. Sem macro
  8. Sem Swizzle
  9. Simples

KLSingleton


11
Estou usando o seu NSSingleton para o meu projeto e parece ser incompatível com o KVO. O problema é que o KVO cria subclasse para cada objeto KVO com o prefixo NSKVONotifying_ MyClass . E faz com que o MyClass + inicialize e -init os métodos a serem chamados duas vezes.
Oleg Trakhman

Testei isso no Xcode mais recente e não tive problemas para registrar ou receber eventos do KVO. Você pode verificar isso com o seguinte código: gist.github.com/3065038 Como mencionei no Twitter, os métodos + initialize são chamados uma vez para NSSingleton e uma vez para cada subclasse. Esta é uma propriedade do Objective-C.
kevinlawler

Se você adicionar NSLog(@"initialize: %@", NSStringFromClass([self class]));ao +initializemétodo, poderá verificar se as classes foram inicializadas apenas uma vez.
kevinlawler

NSLog (@ "inicialize:% @", NSStringFromClass ([classe própria]));
11118 Oleg Trakhman

Você também pode querer que seja compatível com o IB. O meu é: stackoverflow.com/questions/4609609/…
Dan Rosenstark 5/12/12

0

Você não deseja sincronizar consigo mesmo ... Como o objeto próprio ainda não existe! Você acaba bloqueando um valor de ID temporário. Você deseja garantir que ninguém mais possa executar métodos de classe (sharedInstance, aloc, alocWithZone :, etc), portanto, é necessário sincronizar no objeto 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

11
O restante dos métodos, métodos de acesso, métodos de mutação etc. devem ser sincronizados automaticamente. Todos os métodos e inicializadores de classe (+) (e provavelmente -dealloc) devem ser sincronizados no objeto de classe. Você pode evitar a sincronização manual se usar as propriedades do Objective-C 2.0 em vez dos métodos de acessador / mutador. Todos object.property e object.property = foo são automaticamente sincronizados com o próprio.
Rob Dotson

3
Por favor, explique por que você acha que o selfobjeto não existe em um método de classe. O tempo de execução determina qual implementação de método chamar com base exatamente no mesmo valor que fornece selfa cada método (classe ou instância).
dreamlax

2
Dentro de um método de classe, self está o objeto de classe. Tente você mesmo:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
jscs

0

Só queria deixar isso aqui para não perder. A vantagem deste é que ele é utilizável no InterfaceBuilder, que é uma enorme vantagem. Isso é retirado de outra pergunta que eu fiz :

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;
}

0
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

0

Sei que existem muitos comentários sobre essa "pergunta", mas não vejo muitas pessoas sugerindo o uso de uma macro para definir o singleton. É um padrão tão comum e uma macro simplifica bastante o singleton.

Aqui estão as macros que escrevi com base nas várias implementações de Objc que eu já vi.

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; \
}

Exemplo 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 que uma macro de interface quando está quase vazia? Consistência do código entre os arquivos de cabeçalho e código; manutenção caso você queira adicionar métodos mais automáticos ou alterá-los.

Estou usando o método initialize para criar o singleton, como é usado na resposta mais popular aqui (no momento da redação).


0

Com os métodos da classe Objective C, podemos evitar o uso do padrão singleton da maneira usual, a partir de:

[[Librarian sharedInstance] openLibrary]

para:

[Librarian openLibrary]

envolvendo a classe dentro de outra classe que apenas possui métodos de classe , para que não haja chance de criar instâncias duplicadas acidentalmente, pois não estamos criando nenhuma instância!

Eu escrevi um blog mais detalhado aqui :)


Seu link não funciona mais.
i_am_jorf

0

Para estender o exemplo 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;
}

0

Meu jeito é simples assim:

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;
}

Se o singleton já estiver inicializado, o bloco LOCK não será inserido. A segunda verificação se (! Inicializado) é para garantir que ainda não esteja inicializado quando o encadeamento atual adquire o LOCK.


Não está claro que a marcação initializedcomo volatileé suficiente. Consulte aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

Eu não li todas as soluções, então perdoe se esse código é redundante.

Esta é a implementação mais segura de threads na minha opinião.

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}

-4

Eu costumo usar código semelhante ao da resposta de Ben Hoffstein (que também saí da Wikipedia). Eu o uso pelas razões declaradas por Chris Hanson em seu comentário.

No entanto, às vezes tenho a necessidade de colocar um singleton em um NIB e, nesse caso, uso o seguinte:

@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

Deixo a implementação de -retain(etc.) para o leitor, embora o código acima seja tudo o que você precisa em um ambiente de coleta de lixo.


2
Seu código não é seguro para threads. Ele usa sincronizado no método de alocação, mas não no método init. A verificação do bool inicializado não é segura para threads.
Mecki 29/06/09

-5

A resposta aceita, embora seja compilada, está incorreta.

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

De acordo com a documentação da Apple:

... Você pode adotar uma abordagem semelhante para sincronizar os métodos de classe da classe associada, usando o objeto Class em vez de self.

Mesmo se usar auto-obras, não deveria e isso parece um erro de copiar e colar para mim. A implementação correta para um método de fábrica de classe seria:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

6
auto certamente faz existir, escopo de classe. Refere-se à classe em vez da instância da classe. Classes são (principalmente) objetos de primeira classe.
schwa

Por que você coloca @synchroninzed WITHIN um método?
User4951 4/04

11
Como schwa já disse, self é o objeto de classe dentro de um método de classe. Veja meu comentário para um snippet demonstrando isso.
JSCs

selfexiste, mas usá-lo como o identificador passado para @synchronizedsincronizará o acesso aos métodos da instância. Como @ user490696 aponta, há casos (como singletons) em que é preferível usar o objeto de classe. Do Guia de programação do Obj-C:You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
quellish
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.