Como implemento um singleton Objective-C que seja compatível com o ARC?

172

Como converter (ou criar) uma classe singleton que compila e se comporta corretamente ao usar a contagem automática de referência (ARC) no Xcode 4.2?

cescofry
fonte
1
Recentemente, encontrei um artigo de Matt Galloway detalhando bastante os Singletons para ambientes de gerenciamento de memória ARC e manual. galloway.me.uk/tutorials/singleton-classes
cescofry

Respostas:

391

Exatamente da mesma maneira que você (deveria) já estava fazendo isso:

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}
Nick Forge
fonte
9
Você simplesmente não fazer nada do pokey gerenciamento de memória piegas Apple usou para recomendar em developer.apple.com/library/mac/documentation/Cocoa/Conceptual/...
Christopher Pickslay
1
@MakingScienceFictionFact, você pode querer dar uma olhada neste post
kervich
6
As staticvariáveis @David declaradas em um método / função são as mesmas que uma staticvariável declarada fora de um método / função, elas são válidas apenas no escopo desse método / função. Cada execução separada do +sharedInstancemétodo (mesmo em threads diferentes) 'verá' a mesma sharedInstancevariável.
Nick Forge
9
E se alguém chamar [[MyClass assign] init]? Isso criaria um novo objeto. Como podemos evitar isso (além de declarar estática MyClass * sharedInstance = nil fora do método).
Ricardo Sanchez-Saez
2
Se outro programador errar e chamar init quando deveria ter chamado sharedInstance ou similar, é erro deles. Subverter os fundamentos e contratos básicos da linguagem para impedir que outros cometerem erros potencialmente parece bastante errado. Há mais discussões em boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus
8

se você deseja criar outra instância, conforme necessário. faça isso:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

caso contrário, você deve fazer o seguinte:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}
DongXu
fonte
1
Verdadeiro / Falso: O dispatch_once()bit significa que você não obterá instâncias adicionais, mesmo no primeiro exemplo ...?
Olie
4
@ Oh: Falso, porque o código do cliente pode fazer [[MyClass alloc] init]e ignorar o sharedInstanceacesso. DongXu, você deve ler o artigo de Peter Hosey em Singleton . Se você deseja substituir allocWithZone:para impedir que mais instâncias sejam criadas, também deve substituir initpara impedir que a instância compartilhada seja reinicializada.
JSCs
Ok, foi o que pensei, daí a allocWithZone:versão. THX.
Olie
2
Isso rompe completamente o contrato de assignWithZone.
Occulus
1
singleton significa apenas "apenas um objeto na memória a qualquer momento", isso é uma coisa, ser reinicializado é outra coisa.
DongXu
5

Esta é uma versão para ARC e não-ARC

Como usar:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end
Igor
fonte
2

Este é o meu padrão no ARC. Satisfaz novo padrão usando o GCD e também satisfaz o antigo padrão de prevenção de instanciação da Apple.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end
eonil
fonte
1
Isso não resultará em c1uma instância da AAAsuperclasse de? Você precisa chamar +allocem self, e não sobre super.
Nick Forge
@NickForge supernão significa o objeto de superclasse. Você não pode obter um objeto de superclasse Apenas significa rotear mensagens para a versão de superclasse do método. superainda aponta selfclasse. Se você deseja obter um objeto de superclasse, precisa obter funções de reflexão em tempo de execução.
eonil
@NickForge E o -allocWithZone:método é apenas uma cadeia simples para a função de alocação do tempo de execução para oferecer um ponto de substituição. Então, finalmente, o selfponteiro == objeto de classe atual será passado para o alocador e, finalmente, a AAAinstância será alocada.
eonil
você está correto, eu esqueci as sutilezas de como superfunciona nos métodos de classe.
Nick Forge
Lembre-se de usar #import <objc / objc-runtime.h>
Ryan Heitner
2

Leia esta resposta e depois leia a outra resposta.

Você deve primeiro saber o que significa um Singleton e quais são seus requisitos, se você não o entender, do que você não entenderá a solução - de maneira alguma!

Para criar um Singleton com sucesso, você deve poder fazer o seguinte 3:

  • Se houve uma condição de corrida , não devemos permitir que várias instâncias de sua SharedInstance sejam criadas ao mesmo tempo!
  • Lembre-se e mantenha o valor entre várias invocações.
  • Crie apenas uma vez. Controlando o ponto de entrada.

dispatch_once_tajuda a resolver uma condição de corrida, permitindo apenas que seu bloco seja despachado uma vez.

Staticajuda a "lembrar" seu valor em qualquer número de invocações. Como isso se lembra? Ele não permite que nenhuma nova instância com esse nome exato da sua sharedInstance seja criada novamente, apenas funciona com a que foi criada originalmente.

Não usando chamada alloc init(ou seja, ainda temos alloc initmétodos, pois somos uma subclasse NSObject, embora NÃO devamos usá-los) em nossa classe sharedInstance, conseguimos isso usando +(instancetype)sharedInstance, que é limitado a ser iniciado apenas uma vez , independentemente de várias tentativas de threads diferentes ao mesmo tempo e lembre-se de seu valor.

Alguns dos Singletons do sistema mais comuns que acompanham o próprio cacau são:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

Basicamente, qualquer coisa que precisaria ter efeito centralizado precisaria seguir algum tipo de padrão de design Singleton.

Mel
fonte
1

Como alternativa, o Objective-C fornece o método de inicialização + (void) para o NSObject e todas as suas subclasses. É sempre chamado antes de qualquer método da classe.

Eu configurei um ponto de interrupção em uma vez no iOS 6 e o ​​dispatch_once apareceu nos quadros da pilha.

Walt Sellers
fonte
0

Classe Singleton: Ninguém pode criar mais de um objeto de classe em qualquer caso ou de qualquer maneira.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}
Yogi
fonte
1
Se alguém chama init, init chama sharedInstance, sharedInstance chama init, init chama sharedInstance uma segunda vez e depois falha! Primeiro, este é um loop infinito de recursão. Segundo, a segunda iteração de chamar dispatch_once falhará porque não poderá ser chamada novamente de dentro de dispatch_once.
Chuck Krutsinger
0

Existem dois problemas com a resposta aceita, que podem ou não ser relevantes para o seu propósito.

  1. Se a partir do método init, de alguma forma, o método sharedInstance for chamado novamente (por exemplo, porque outros objetos são construídos a partir daí e que usam o singleton), ele causará um estouro de pilha.
  2. Para hierarquias de classes, existe apenas um singleton (a saber: a primeira classe na hierarquia na qual o método sharedInstance foi chamado), em vez de um singleton por classe concreta na hierarquia.

O código a seguir cuida desses dois problemas:

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackoverflow problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}
Werner Altewischer
fonte
-2
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

Espero que o código acima o ajude.

Kiran
fonte
-2

se você precisar criar singleton rapidamente,

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

ou

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

você pode usar dessa maneira

let sharedClass = LibraryAPI.sharedInstance
muhammedkasva
fonte