Wie implementiere ich einen Objective-C-Singleton, der mit ARC kompatibel ist?

172

Wie konvertiere (oder erstelle) ich eine Singleton-Klasse, die bei Verwendung der automatischen Referenzzählung (ARC) in Xcode 4.2 kompiliert wird und sich korrekt verhält?

Cescofry
quelle
1
Ich habe kürzlich einen Artikel von Matt Galloway gefunden, der Singletons sowohl für ARC- als auch für manuelle Speicherverwaltungsumgebungen ausführlich behandelt. galloway.me.uk/tutorials/singleton-classes
cescofry

Antworten:

391

Genau so, wie Sie es bereits hätten tun sollen:

+ (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
quelle
9
Sie tun einfach nichts von der Speicherverwaltung, die Apple in developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Christopher Pickslay
1
@ MakingScienceFictionFact, möchten Sie vielleicht einen Blick auf diesen Beitrag
werfen
6
@ David- staticVariablen, die innerhalb einer Methode / Funktion deklariert wurden, sind dieselben wie eine staticVariable, die außerhalb einer Methode / Funktion deklariert wurde. Sie sind nur im Rahmen dieser Methode / Funktion gültig. Jeder separater Lauf durch das +sharedInstanceVerfahren (auch auf verschiedene Threads) wird ‚sehen‘ die gleiche sharedInstanceVariable.
Nick Forge
9
Was ist, wenn jemand [[MyClass alloc] init] aufruft? Das würde ein neues Objekt schaffen. Wie können wir dies vermeiden (außer statische MyClass * sharedInstance = nil außerhalb der Methode zu deklarieren).
Ricardo Sanchez-Saez
2
Wenn ein anderer Programmierer Fehler macht und init aufruft, wenn er sharedInstance oder ähnliches hätte aufrufen sollen, ist dies sein Fehler. Es scheint völlig falsch, die Grundlagen und Grundverträge der Sprache zu untergraben, um zu verhindern, dass andere potenziell Fehler machen. Es gibt mehr Diskussionen unter gelangtzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus
8

Wenn Sie nach Bedarf eine andere Instanz erstellen möchten, gehen Sie wie folgt vor:

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

Andernfalls sollten Sie Folgendes tun:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}
DongXu
quelle
1
Richtig / Falsch: Das dispatch_once()Bit bedeutet, dass Sie auch im ersten Beispiel keine zusätzlichen Instanzen erhalten ...?
Olie
4
@Olie: Falsch, weil Client-Code [[MyClass alloc] init]den sharedInstanceZugriff ausführen und umgehen kann . DongXu, du solltest dir Peter Hoseys Singleton-Artikel ansehen . Wenn Sie überschreiben möchten allocWithZone:, um zu verhindern, dass weitere Instanzen erstellt werden, sollten Sie auch überschreiben init, um zu verhindern, dass die gemeinsam genutzte Instanz neu initialisiert wird.
Jscs
Ok, das habe ich mir gedacht, daher die allocWithZone:Version. Vielen Dank.
Olie
2
Dies bricht den Vertrag von allocWithZone vollständig.
Occulus
1
Singleton bedeutet nur "immer nur ein Objekt im Speicher", dies ist eine Sache, eine Neuinitialisierung ist eine andere Sache.
DongXu
5

Dies ist eine Version für ARC und Nicht-ARC

Wie benutzt man:

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
quelle
2

Dies ist mein Muster unter ARC. Erfüllt neue Muster mit GCD und erfüllt auch Apples altes Muster zur Verhinderung der Instanziierung.

@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
quelle
1
Wird dies nicht dazu führen, c1dass es sich um eine Instanz der AAAOberklasse handelt? Sie müssen rufen Sie +allocauf self, nicht auf super.
Nick Forge
@NickForge superbedeutet nicht das Super-Class-Objekt. Sie können kein Super-Class-Objekt abrufen. Dies bedeutet lediglich, dass Nachrichten an die Super-Class-Version der Methode weitergeleitet werden. superPunkte selfKlasse noch. Wenn Sie ein erstklassiges Objekt erhalten möchten, benötigen Sie Laufzeitreflexionsfunktionen.
Eonil
@NickForge And- -allocWithZone:Methode ist nur eine einfache Kette zur Zuordnungsfunktion der Laufzeit, um einen übergeordneten Punkt zu bieten. Also letztlich selfZeiger == aktuelles Klassenobjekt wird allocator weitergegeben werden, und schließlich AAAwird Instanz zugewiesen werden.
Eonil
Sie haben Recht, ich hatte die Feinheiten vergessen, wie superin Klassenmethoden funktioniert.
Nick Forge
Denken Sie daran, #import <objc / objc-runtime.h> zu verwenden
Ryan Heitner
2

Lesen Sie diese Antwort und lesen Sie dann die andere Antwort.

Sie müssen zuerst wissen, was ein Singleton bedeutet und welche Anforderungen er hat. Wenn Sie ihn nicht verstehen, werden Sie die Lösung überhaupt nicht verstehen!

Um einen Singleton erfolgreich zu erstellen, müssen Sie in der Lage sein, die folgenden 3 Schritte auszuführen:

  • Wenn eine Racebedingung vorliegt , dürfen nicht mehrere Instanzen Ihrer SharedInstance gleichzeitig erstellt werden!
  • Denken Sie daran und behalten Sie den Wert bei mehreren Aufrufen bei.
  • Erstellen Sie es nur einmal. Durch Steuerung des Einstiegspunktes.

dispatch_once_thilft Ihnen, eine Rennbedingung zu lösen , indem Sie den Block nur einmal versenden lassen.

Statichilft Ihnen, sich über eine beliebige Anzahl von Aufrufen an seinen Wert zu erinnern. Wie erinnert es sich? Es kann keine neue Instanz mit dem genauen Namen Ihrer sharedInstance erneut erstellt werden. Es funktioniert nur mit der ursprünglich erstellten.

Nicht mit Aufruf alloc init(dh wir haben noch alloc initMethoden , da wir eine NSObject Unterklasse sind, obwohl wir sie nicht verwenden sollen) auf unserer sharedInstance Klasse, wir dies erreichen durch die Verwendung +(instancetype)sharedInstance, die nur begrenzt sein wird einmal ausgelöst , unabhängig von mehreren Versuchen von verschiedenen Threads zur gleichen Zeit und erinnern Sie sich an seinen Wert.

Einige der häufigsten System-Singletons, die mit Cocoa selbst geliefert werden, sind:

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

Grundsätzlich müsste alles, was einen zentralisierten Effekt haben müsste, einer Art Singleton-Entwurfsmuster folgen.

Honig
quelle
1

Alternativ bietet Objective-C die Initialisierungsmethode + (void) für NSObject und alle seine Unterklassen. Es wird immer vor allen Methoden der Klasse aufgerufen.

Ich habe einmal in iOS 6 einen Haltepunkt in einem gesetzt und dispatch_once wurde in den Stapelrahmen angezeigt.

Walt Sellers
quelle
0

Singleton-Klasse: Niemand kann auf jeden Fall oder auf irgendeine Weise mehr als ein Klassenobjekt erstellen.

+ (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
quelle
1
Wenn jemand init aufruft, ruft init sharedInstance auf, sharedInstance ruft init auf, init ruft sharedInstance ein zweites Mal auf und stürzt dann ab! Erstens ist dies eine Endlosrekursionsschleife. Zweitens stürzt die zweite Iteration des Aufrufs von dispatch_once ab, da sie innerhalb von dispatch_once nicht erneut aufgerufen werden kann.
Chuck Krutsinger
0

Es gibt zwei Probleme mit der akzeptierten Antwort, die für Ihren Zweck relevant sein können oder nicht.

  1. Wenn von der init-Methode die sharedInstance-Methode erneut aufgerufen wird (z. B. weil von dort aus andere Objekte erstellt werden, die den Singleton verwenden), führt dies zu einem Stapelüberlauf.
  2. Für Klassenhierarchien gibt es nur einen Singleton (nämlich die erste Klasse in der Hierarchie, für die die sharedInstance-Methode aufgerufen wurde) anstelle eines Singletons pro konkreter Klasse in der Hierarchie.

Der folgende Code behebt diese beiden Probleme:

+ (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
quelle
-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

Hoffe, dass der obige Code Abhilfe schafft.

kiran
quelle
-2

Wenn Sie schnell Singleton erstellen müssen,

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

oder

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

Sie können diesen Weg verwenden

let sharedClass = LibraryAPI.sharedInstance
Muhammedkasva
quelle