Jak zaimplementować singleton Objective-C, który jest zgodny z ARC?


172

Jak przekonwertować (lub utworzyć) klasę pojedynczą, która kompiluje się i zachowuje poprawnie podczas korzystania z automatycznego liczenia odwołań (ARC) w Xcode 4.2?


1
Niedawno znalazłem artykuł Matta Gallowaya, w którym dogłębnie zajmował się Singletons zarówno dla środowisk ARC, jak i ręcznego zarządzania pamięcią. galloway.me.uk/tutorials/singleton-classes
cescofry

Odpowiedzi:


391

Dokładnie w taki sam sposób, w jaki (powinieneś) już to robić:

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

9
Po prostu nie robisz żadnego z hokey zarządzania pamięcią, które Apple polecał w developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ ...
hokeyów Christopher Pickslay

1
@MakingScienceFictionFact, możesz rzucić okiem na ten post
kervich

6
@David staticZmienne zadeklarowane w metodzie / funkcji są takie same jak staticzmienna zadeklarowana poza metodą / funkcją, są po prostu poprawne tylko w zakresie tej metody / funkcji. Każde oddzielne wykonanie tej +sharedInstancemetody (nawet w różnych wątkach) „zobaczy” tę samą sharedInstancezmienną.
Nick Forge

9
A co jeśli ktoś wywoła [[MyClassocation] init]? To stworzyłoby nowy obiekt. Jak możemy tego uniknąć (poza deklarowaniem statycznej MyClass * sharedInstance = nil poza metodą).
Ricardo Sanchez-Saez,

2
Jeśli inny programista pomyli się i wywoła init, gdy powinien był wywołać sharedInstance lub coś podobnego, to jego błąd. Podważanie podstaw i podstawowych kontraktów języka w celu powstrzymania innych od potencjalnego popełniania błędów wydaje się całkowicie błędne. Więcej dyskusji jest na boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus

8

jeśli chcesz w razie potrzeby utworzyć inną instancję. zrób to:

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

w przeciwnym razie powinieneś to zrobić:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

1
Prawda / fałsz: dispatch_once()Bit oznacza, że ​​nie otrzymasz dodatkowych instancji, nawet w pierwszym przykładzie ...?
Olie

4
@Olie: Fałsz, ponieważ kod klienta może zrobić [[MyClass alloc] init]i ominąć sharedInstancedostęp. DongXu, powinieneś spojrzeć na artykuł Petera Hoseya w Singleton . Jeśli zamierzasz przesłonić, allocWithZone:aby zapobiec tworzeniu większej liczby wystąpień, powinieneś również przesłonić, initaby zapobiec ponownej inicjalizacji wystąpienia współużytkowanego.
jscs

Ok, tak myślałem, stąd allocWithZone:wersja. Dzięki.
Olie

2
To całkowicie zrywa umowę z systemem assignWithZone.
occulus

1
singleton oznacza po prostu „tylko jeden obiekt w pamięci w dowolnym momencie”, to jedno, ponowne zainicjowanie to co innego.
DongXu,

5

To jest wersja dla ARC i non-ARC

Jak używać:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

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

2

To jest mój wzór pod ARC. Zgodny z nowym wzorcem przy użyciu GCD, a także ze starym wzorcem zapobiegania instancjom firmy 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

1
Czy to nie spowoduje c1bycia instancją AAAsuperklasy? Trzeba zadzwonić +allocna selfnie na super.
Nick Forge,

@NickForge supernie oznacza obiektu super-klasy. Nie możesz uzyskać obiektu super-klasy. Oznacza to po prostu kierowanie wiadomości do wersji metody super-klasy. supernadal punktuje selfklasę. Jeśli chcesz uzyskać obiekt super-klasy, musisz uzyskać funkcje odbicia w czasie wykonywania.
eonil

-allocWithZone:Metoda @NickForge And jest po prostu prostym łańcuchem do funkcji alokacji środowiska wykonawczego, oferując punkt nadpisania. Ostatecznie więc selfpointer == bieżący obiekt klasy zostanie przekazany do alokatora, a na koniec AAAzostanie przydzielona instancja.
eonil

masz rację, zapomniałem o subtelnościach tego, jak superdziała w metodach klasowych.
Nick Forge,

Pamiętaj, aby użyć #import <objc / objc-runtime.h>
Ryan Heitner

2

Przeczytaj tę odpowiedź, a następnie przejdź i przeczytaj drugą odpowiedź.

Musisz najpierw wiedzieć, co oznacza Singleton i jakie są jego wymagania, jeśli go nie rozumiesz, to nie zrozumiesz rozwiązania - w ogóle!

Aby pomyślnie utworzyć singleton, musisz umieć wykonać następujące 3:

  • Jeśli wystąpił stan wyścigu , nie możemy pozwolić na jednoczesne tworzenie wielu wystąpień Twojej SharedInstance!
  • Pamiętaj i zachowaj wartość wśród wielu wywołań.
  • Utwórz go tylko raz. Kontrolując punkt wejścia.

dispatch_once_tpomaga rozwiązać stan wyścigu, zezwalając na jednorazowe wysłanie jego bloku.

Staticpomaga „zapamiętać” jego wartość w dowolnej liczbie wywołań. Jak to zapamiętuje? Nie pozwala na ponowne utworzenie żadnej nowej instancji o tej dokładnej nazwie Twojej SharedInstance, po prostu działa z tą, która została pierwotnie utworzona.

Nie używając wywoływania alloc init(tj. Nadal mamy alloc initmetody, ponieważ jesteśmy podklasą NSObject, chociaż NIE powinniśmy ich używać) w naszej klasie sharedInstance, osiągamy to przez użycie +(instancetype)sharedInstance, które jest ograniczone do zainicjowania tylko raz , niezależnie od wielu prób z różnych wątków w tym samym czasie i pamiętaj o jego wartości.

Niektóre z najpopularniejszych singletonów systemowych, które są dostarczane z samym kakao, to:

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

Zasadniczo wszystko, co musiałoby mieć scentralizowany efekt, musiałoby być zgodne z pewnym wzorcem projektowym typu Singleton.


1

Alternatywnie Objective-C zapewnia metodę inicjalizacji + (void) dla NSObject i wszystkich jego podklas. Jest zawsze wywoływana przed wszystkimi metodami klasy.

Ustawiłem punkt przerwania w jednym raz w iOS 6 i dispatch_once pojawił się w ramkach stosu.


0

Klasa pojedyncza: nikt nie może utworzyć więcej niż jednego obiektu klasy w żadnym przypadku ani w żaden sposób.

+ (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];
}

1
Jeśli ktoś wywoła init, init wywoła sharedInstance, sharedInstance wywołuje init, init po raz drugi wywołuje sharedInstance, a następnie zawiesza się! Po pierwsze, jest to nieskończona pętla rekurencji. Po drugie, druga iteracja wywołania dispatch_once ulegnie awarii, ponieważ nie można go ponownie wywołać z wewnątrz dispatch_once.
Chuck Krutsinger,

0

Z zaakceptowaną odpowiedzią wiążą się dwa problemy, które mogą, ale nie muszą być istotne dla Twojego celu.

  1. Jeśli z metody init zostanie w jakiś sposób ponownie wywołana metoda sharedInstance (np. Ponieważ budowane są stamtąd inne obiekty, które używają singletona), spowoduje to przepełnienie stosu.
  2. W przypadku hierarchii klas istnieje tylko jeden singleton (mianowicie: pierwsza klasa w hierarchii, na której została wywołana metoda sharedInstance), zamiast jednego singletonu na konkretną klasę w hierarchii.

Poniższy kod rozwiązuje oba te problemy:

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

-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

Mam nadzieję, że powyższy kod pomoże.


-2

jeśli potrzebujesz szybko stworzyć singletona,

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

lub

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

możesz użyć w ten sposób

let sharedClass = LibraryAPI.sharedInstance
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.