Wie sollte mein Objective-C-Singleton aussehen? [geschlossen]

334

Meine Singleton-Accessor-Methode ist normalerweise eine Variante von:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Was könnte ich tun, um dies zu verbessern?

Schwa
quelle
27
Was Sie haben, ist in Ordnung, obwohl Sie die globale Variablendeklaration in Ihre + Instanzmethode verschieben können (der einzige Ort, an dem sie verwendet werden muss, es sei denn, Sie erlauben auch das Festlegen) und einen Namen wie + defaultMyClass oder + verwenden sharedMyClass für Ihre Methode. + Instanz ist nicht absichtsreich.
Chris Hanson
Da es unwahrscheinlich ist, dass sich die 'Antwort' auf diese Frage bald ändert, setze ich eine historische Sperre für die Frage. Zwei Gründe 1) Viele Ansichten, Stimmen und gute Inhalte 2) Um das Jojoing von offen / geschlossen zu verhindern. Es war eine großartige Frage für seine Zeit, aber Fragen dieser Art sind für den Stapelüberlauf nicht geeignet. Wir haben jetzt Code Review zur Überprüfung des Arbeitscodes. Bitte nehmen Sie die gesamte Diskussion dieser Frage zu dieser Meta-Frage .
George Stocker

Antworten:

207

Eine andere Möglichkeit ist die Verwendung der +(void)initializeMethode. Aus der Dokumentation:

Die Laufzeit wird initializegenau einmal an jede Klasse in einem Programm gesendet, kurz bevor die Klasse oder eine von ihr geerbte Klasse ihre erste Nachricht aus dem Programm heraus sendet . (Daher wird die Methode möglicherweise nie aufgerufen, wenn die Klasse nicht verwendet wird.) Die Laufzeit sendet die initializeNachricht threadsicher an Klassen. Superklassen erhalten diese Nachricht vor ihren Unterklassen.

Sie könnten also etwas Ähnliches tun:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}
Robbie Hanson
quelle
7
Was macht der BOOL, wenn die Laufzeit dies nur einmal aufruft? Ist das eine Vorsichtsmaßnahme für den Fall, dass jemand diese Funktion explizit aus seinem Code aufruft?
Aftermathew
5
Ja, dies ist eine Vorsichtsmaßnahme, da die Funktion auch direkt aufgerufen werden kann.
Robbie Hanson
33
Dies ist auch erforderlich, da es Unterklassen geben kann. Wenn sie +initializeihre Oberklassen nicht überschreiben , wird die Implementierung aufgerufen, wenn die Unterklasse zum ersten Mal verwendet wird.
Sven
3
@Paul Sie können die releaseMethode überschreiben und leer machen. :)
4
@aryaxt: Aus den aufgelisteten Dokumenten geht hervor, dass dies bereits threadsicher ist. Der Aufruf erfolgt also einmal pro Laufzeitperiode. Dies scheint die richtige, threadsichere und optimal effiziente Lösung zu sein.
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

[Quelle]

Ben Hoffstein
quelle
7
Dies ist alles, was Sie normalerweise für Singletons verwenden sollten. Wenn Sie Ihre Klassen separat instanziierbar halten, können Sie sie unter anderem einfacher testen, da Sie separate Instanzen testen können, anstatt ihren Status zurücksetzen zu können.
Chris Hanson
3
Stig Brautaset: Nein, es ist nicht in Ordnung, das @synchronized in diesem Beispiel wegzulassen. Es ist dazu da, die mögliche Race-Bedingung von zwei Threads zu behandeln, die diese statische Funktion gleichzeitig ausführen, wobei beide gleichzeitig den Test "if (! SharedSingleton)" bestehen und somit zu zwei [MySingleton-Zuweisungen] führen. .. Der @synchronisierte {Scope-Block} zwingt diesen hypothetischen zweiten Thread, darauf zu warten, dass der erste Thread den {Scope-Block} verlässt, bevor er damit fortfahren darf. Ich hoffe das hilft! =)
MechEthan
3
Was hindert jemanden daran, immer noch eine eigene Instanz des Objekts zu erstellen? MySingleton *s = [[MySingelton alloc] init];
Lindon Fox
1
@ Lindonfox Was ist die Antwort auf Ihre Frage?
Raffi Khatchadourian
1
@ Raffi - Entschuldigung, ich denke, ich muss vergessen haben, meine Antwort einzufügen. Wie auch immer, ich habe das Buch bekommen Pro Objective-C Design Patterns for iOSund es beschreibt, wie man ein "strenges" Singelton macht. Da Sie die initiierenden Methoden nicht privat machen können, müssen Sie die Methodenzuweisung und -kopie überschreiben. Wenn Sie also versuchen, etwas Ähnliches zu tun, [[MySingelton alloc] init]wird ein Laufzeitfehler angezeigt (leider kein Kompilierungsfehler). Ich verstehe nicht, wie alle Details der Objekterstellung, aber Sie implementieren, + (id) allocWithZone:(NSZone *)zonedie insharedSingleton
Lindon Fox
59

Gemäß meiner anderen Antwort unten denke ich, dass Sie tun sollten:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}
Colin Barrett
quelle
6
Kümmere dich nicht um alles, was du oben machst. Machen Sie Ihre (hoffentlich extrem wenigen) Singletons separat instanziierbar und haben Sie nur eine gemeinsame / Standardmethode. Was Sie getan haben, ist nur notwendig, wenn Sie wirklich NUR eine einzelne Instanz Ihrer Klasse wollen. Was du nicht tust, insb. für Unit-Tests.
Chris Hanson
Die Sache ist, dass dies der Apple-Beispielcode für "Erstellen eines Singletons" ist. Aber ja, du hast absolut recht.
Colin Barrett
1
Der Apple-Beispielcode ist korrekt, wenn Sie einen "echten" Singleton möchten (dh ein Objekt, das immer nur einmal instanziiert werden kann), aber wie Chris sagt, ist dies selten das, was Sie wollen oder brauchen, während Sie eine Art einstellbare gemeinsam genutzte Instanz benötigen normalerweise wollen.
Luke Redpath
Hier ist ein Makro für die obige Methode: gist.github.com/1057420 . Das benutze ich.
Kobski
1
Abgesehen von Unit-Tests spricht nichts gegen diese Lösung, richtig? Und es ist schnell und sicher.
LearnCocos2D
58

Da Kendall einen threadsicheren Singleton veröffentlicht hat, der versucht, Sperrkosten zu vermeiden, dachte ich, ich würde auch einen werfen:

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

Okay, lassen Sie mich erklären, wie das funktioniert:

  1. Schneller Fall: In der normalen Ausführung sharedInstancewurde bereits festgelegt, sodass die whileSchleife niemals ausgeführt wird und die Funktion nach einfachem Testen der Existenz der Variablen zurückkehrt.

  2. Langsamer Fall: Wenn sharedInstancenicht vorhanden, wird eine Instanz zugewiesen und mithilfe von Compare And Swap ('CAS') in diese kopiert.

  3. Stritten Fall: Wenn zwei Threads beide Versuch Aufruf sharedInstancezur gleichen Zeit und sharedInstance existiert nicht in der gleichen Zeit , dann werden sie beiden initialize neuen Instanzen der Singleton und versuchen, CAS es in der richtigen Position. Wer auch immer den CAS gewinnt, gibt sofort zurück, wer verliert, gibt die gerade zugewiesene Instanz frei und gibt die (jetzt gesetzte) zurück sharedInstance. Die Single OSAtomicCompareAndSwapPtrBarrierfungiert sowohl als Schreibbarriere für den Einstellungs-Thread als auch als Lesebarriere für den Test-Thread.

Louis Gerbarg
quelle
18
Dies ist höchstens ein völliger Overkill, der während der Lebensdauer einer Anwendung auftreten kann. Trotzdem ist es genau richtig und die Compare-and-Swap-Technik ist ein nützliches Werkzeug, also +1.
Steve Madsen
Schöne Antwort - die OSAtomic-Familie ist eine gute Sache zu wissen
Bill
1
@ Louis: Erstaunliche, wirklich aufschlussreiche Antwort! Eine Frage: Was sollte meine initMethode in Ihrem Ansatz tun? sharedInstanceIch glaube, es ist keine gute Idee, bei der Initialisierung eine Ausnahme auszulösen. Was ist dann zu tun, um zu verhindern, dass Benutzer initmehrmals direkt anrufen ?
Matm
2
Ich verhindere es im Allgemeinen nicht. Es gibt oft triftige Gründe, um zuzulassen, dass ein Singleton im Allgemeinen mehrfach instanziiert wird. Die meisten Gründe gelten für bestimmte Arten von Komponententests. Wenn ich wirklich eine einzelne Instanz erzwingen wollte, würde ich wahrscheinlich die init-Methode überprüfen lassen, um festzustellen, ob die globale vorhanden ist, und wenn ja, würde ich sie selbst freigeben und die globale zurückgeben.
Louis Gerbarg
1
@ Tony etwas spät in der Antwort, aber OSAtomicCompareAndSwapPtrBarrier erfordert eine flüchtige. Vielleicht besteht das flüchtige Schlüsselwort darin, den Compiler davon abzuhalten, die Prüfung zu optimieren? Siehe: stackoverflow.com/a/5334727/449161 und developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn
14
statische MyClass * sharedInst = nil;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == nil) {
            / * sharedInst in init eingerichtet * /
            Selbstzuweisung init;
        }}
    }}
    return sharedInst;
}}

- (id) init
{
    if (sharedInst! = nil) {
        [NSException erhöhen: NSInternalInconsistencyException
            Format: @ "[% @% @] kann nicht aufgerufen werden; verwenden Sie stattdessen + [% @% @]"],
            NSStringFromClass ([Selbstklasse]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([Selbstklasse]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = self;
        / * Welche Klasse auch immer hier spezifisch ist * /
    }}
    return sharedInst;
}}

/ * Diese machen wahrscheinlich nichts in
   eine GC-App. Hält Singleton
   als tatsächlicher Singleton in a
   Nicht-CG-App
* /
- (NSUInteger) keepCount
{
    return NSUIntegerMax;
}}

- (Einweg ungültig) Freigabe
{
}}

- (id) behalten
{
    return sharedInst;
}}

- (id) Autorelease
{
    return sharedInst;
}}

quelle
3
Ich habe festgestellt, dass sich clang über ein Leck beschwert, wenn Sie das Ergebnis von nicht [[self alloc] init]auf sharedInst zuweisen.
pix0r
Ein solches Init zu untergraben ist ein ziemlich hässlicher Ansatz, IMO. Leg dich nicht mit init und / oder der tatsächlichen Erstellung des Objekts an. Wenn Sie stattdessen einen kontrollierten Zugriffspunkt auf eine gemeinsam genutzte Instanz wählen, ohne Singleton in das Objekt einzubacken, haben Sie später eine glücklichere Zeit, wenn Sie Tests usw. schreiben. Harte Singletons werden viel zu häufig verwendet.
Occulus
12

Bearbeiten: Diese Implementierung ist mit ARC veraltet. Schauen Sie sich bitte an, wie ich einen Objective-C-Singleton implementiere, der mit ARC kompatibel ist. für die korrekte Umsetzung.

Alle Implementierungen von initialize, die ich in anderen Antworten gelesen habe, haben einen gemeinsamen Fehler.

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

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

In der Apple-Dokumentation wird empfohlen, den Klassentyp in Ihrem Initialisierungsblock zu überprüfen. Weil Unterklassen standardmäßig die Initialisierung aufrufen. Es gibt einen nicht offensichtlichen Fall, in dem Unterklassen indirekt über KVO erstellt werden können. Wenn Sie die folgende Zeile in eine andere Klasse einfügen:

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

Objective-C erstellt implizit eine Unterklasse von MySingletonClass, was zu einer zweiten Auslösung von führt +initialize.

Möglicherweise sollten Sie implizit prüfen, ob Ihr Init-Block als solcher doppelt initialisiert ist:

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

Aber du wirst dich in den Fuß schießen; oder schlimmer noch, geben Sie einem anderen Entwickler die Möglichkeit, sich in den Fuß zu schießen.

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

TL; DR, hier ist meine Implementierung

@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

(Ersetzen Sie ZAssert durch unser eigenes Assertionsmakro oder nur durch NSAssert.)

Lorean
quelle
1
Ich würde einfach einfacher leben und es vermeiden, insgesamt zu initialisieren.
Tom Andersen
9

Ich habe eine interessante Variante von sharedInstance, die threadsicher ist, aber nach der Initialisierung nicht gesperrt wird. Ich bin mir noch nicht sicher genug, um die Top-Antwort wie gewünscht zu ändern, aber ich präsentiere sie zur weiteren Diskussion:

// 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;
}
Kendall Helmstetter Gelner
quelle
1
+1 das ist wirklich faszinierend. Ich könnte verwenden, class_replaceMethodum mich sharedInstancein einen Klon von zu verwandeln simpleSharedInstance. Auf diese Weise müssten Sie sich nie wieder Sorgen machen, ein @synchronizedSchloss zu erwerben .
Dave DeLong
Der gleiche Effekt bei Verwendung von exchangeImplementations bedeutet, dass Sie nach init beim Aufrufen von sharedInstance wirklich simpleSharedInstance aufrufen. Eigentlich habe ich mit replaceMethod angefangen, aber ich habe beschlossen, dass es besser ist, die Implementierungen einfach umzuschalten, damit das Original bei Bedarf noch vorhanden ist ...
Kendall Helmstetter Gelner
Bei weiteren Tests konnte replaceMethod nicht zum Laufen gebracht werden. Bei wiederholten Aufrufen wurde der Code weiterhin als original sharedInstance anstelle von simpleSharedInstance bezeichnet. Ich denke, es kann sein, dass beide Methoden auf Klassenebene sind ... Der Ersatz, den ich verwendet habe, war: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); und einige Variationen davon. Ich kann überprüfen, ob der von mir veröffentlichte Code funktioniert, und simpleSharedInstance wird nach dem ersten Durchlauf durch sharedInstance aufgerufen.
Kendall Helmstetter Gelner
Sie können eine thread-sichere Version erstellen, die nach der Initialisierung keine Sperrkosten zahlt, ohne ein paar Laufzeitprobleme zu verursachen. Ich habe unten eine Implementierung veröffentlicht.
Louis Gerbarg
1
+1 tolle Idee. Ich liebe die Dinge, die man mit der Laufzeit machen kann. In den meisten Fällen handelt es sich jedoch wahrscheinlich um eine vorzeitige Optimierung. Wenn ich die Synchronisationskosten wirklich loswerden müsste, würde ich wahrscheinlich die sperrenlose Version von Louis verwenden.
Sven
6

Kurze Antwort: Fabelhaft.

Lange Antwort: So etwas wie ....

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

Lesen Sie unbedingt die Kopfzeile dispatch / einmal.h, um zu verstehen, was los ist. In diesem Fall sind die Header-Kommentare zutreffender als die Dokumente oder die Manpage.

quellish
quelle
5

Ich habe Singleton in eine Klasse gerollt, damit andere Klassen Singleton-Eigenschaften erben können.

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

Und hier ist ein Beispiel für eine Klasse, die Sie Singleton werden möchten.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Die einzige Einschränkung der Singleton-Klasse besteht darin, dass es sich um eine NSObject-Unterklasse handelt. Aber meistens verwende ich Singletons in meinem Code, es handelt sich tatsächlich um NSObject-Unterklassen. Diese Klasse erleichtert mir also das Leben und macht den Code sauberer.

obszön
quelle
Möglicherweise möchten Sie einen anderen Verriegelungsmechanismus verwenden, da dieser @synchronizedschrecklich langsam ist und vermieden werden sollte.
DarkDust
2

Dies funktioniert auch in einer Umgebung ohne Müllabfuhr.

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

Sollte dies nicht threadsicher sein und das teure Sperren nach dem ersten Anruf vermeiden?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}
Jompe
quelle
2
Die hier verwendete doppelt überprüfte Sperrtechnik ist in einigen Umgebungen häufig ein echtes Problem (siehe aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf oder Google it). Bis etwas anderes gezeigt wird, würde ich annehmen, dass Objective-C nicht immun ist. Siehe auch wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen
2

Wie wäre es mit

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

So vermeiden Sie die Synchronisationskosten nach der Initialisierung?

Tony
quelle
Weitere Antworten finden Sie in den Diskussionen zu Dual Checked Locking.
i_am_jorf
1

KLSingleton ist:

  1. Unterklassifizierbar (bis zum n-ten Grad)
  2. ARC-kompatibel
  3. Sicher mit allocundinit
  4. Faul geladen
  5. Gewindesicher
  6. Lock-free (verwendet + initialize, nicht @synchronize)
  7. Makrofrei
  8. Swizzle-frei
  9. Einfach

KLSingleton

Kevinlawler
quelle
1
Ich verwende Ihr NSSingleton für mein Projekt und es scheint mit KVO nicht kompatibel zu sein. Die Sache ist, dass KVO für jedes KVO-Objekt eine Unterklasse mit dem Präfix NSKVONotifying_ MyClass erstellt . Außerdem werden die Methoden MyClass + initialize und -init zweimal aufgerufen.
Oleg Trakhman
Ich habe dies mit dem neuesten Xcode getestet und hatte keine Probleme, mich für KVO-Events zu registrieren oder diese zu empfangen. Sie können dies mit dem folgenden Code überprüfen: gist.github.com/3065038 Wie auf Twitter erwähnt, werden die + Initialisierungsmethoden einmal für NSSingleton und einmal für jede Unterklasse aufgerufen. Dies ist eine Eigenschaft von Objective-C.
Kevinlawler
Wenn Sie NSLog(@"initialize: %@", NSStringFromClass([self class]));der +initializeMethode hinzufügen , können Sie überprüfen, ob die Klassen nur einmal initialisiert werden.
Kevinlawler
NSLog (@ "initialize:% @", NSStringFromClass ([Selbstklasse]));
Oleg Trakhman
Vielleicht möchten Sie auch, dass es IB-kompatibel ist. Meins ist: stackoverflow.com/questions/4609609/…
Dan Rosenstark
0

Sie möchten nicht mit sich selbst synchronisieren ... Da das Selbstobjekt noch nicht existiert! Am Ende sperren Sie einen temporären ID-Wert. Sie möchten sicherstellen, dass niemand anderes Klassenmethoden ausführen kann (sharedInstance, alloc, allocWithZone:, usw.). Daher müssen Sie stattdessen das Klassenobjekt synchronisieren:

@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
Rob Dotson
quelle
1
Die restlichen Methoden, Accessor-Methoden, Mutator-Methoden usw. sollten sich selbst synchronisieren. Alle Klassenmethoden (+) und Initialisierer (und wahrscheinlich -dealloc) sollten auf dem Klassenobjekt synchronisiert werden. Sie können die manuelle Synchronisierung vermeiden, wenn Sie Objective-C 2.0-Eigenschaften anstelle von Accessor / Mutator-Methoden verwenden. Alle object.property und object.property = foo werden automatisch mit self synchronisiert.
Rob Dotson
3
Bitte erläutern Sie, warum Sie der Meinung sind, dass das selfObjekt in einer Klassenmethode nicht vorhanden ist. Die Laufzeit bestimmt, welche Methodenimplementierung aufgerufen werden soll, basierend auf genau demselben Wert, den sie für selfjede Methode (Klasse oder Instanz) bereitstellt .
Dreamlax
2
Innerhalb einer Klassenmethode self befindet sich das Klassenobjekt. Probieren Sie es selbst aus:#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

Ich wollte das nur hier lassen, damit ich es nicht verliere. Der Vorteil von diesem ist, dass es in InterfaceBuilder verwendet werden kann, was ein RIESIGER Vorteil ist. Dies geht aus einer anderen Frage hervor, die ich gestellt habe :

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;
}
Dan Rosenstark
quelle
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
user370199
quelle
0

Ich weiß, dass es viele Kommentare zu dieser "Frage" gibt, aber ich sehe nicht viele Leute, die vorschlagen, ein Makro zu verwenden, um den Singleton zu definieren. Es ist so ein allgemeines Muster und ein Makro vereinfacht den Singleton erheblich.

Hier sind die Makros, die ich basierend auf mehreren Objc-Implementierungen geschrieben habe, die ich gesehen habe.

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

Anwendungsbeispiel:

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

Warum ein Schnittstellenmakro, wenn es fast leer ist? Codekonsistenz zwischen Header- und Codedateien; Wartbarkeit für den Fall, dass Sie weitere automatische Methoden hinzufügen oder ändern möchten.

Ich verwende die Initialisierungsmethode, um den Singleton zu erstellen, wie er in der beliebtesten Antwort hier (zum Zeitpunkt des Schreibens) verwendet wird.

Nate
quelle
0

Mit Objective C-Klassenmethoden können wir einfach vermeiden, das Singleton-Muster auf die übliche Weise zu verwenden:

[[Librarian sharedInstance] openLibrary]

zu:

[Librarian openLibrary]

Wenn Sie die Klasse in eine andere Klasse einbinden , die nur über Klassenmethoden verfügt, besteht keine Möglichkeit, versehentlich doppelte Instanzen zu erstellen, da wir keine Instanz erstellen!

Ich schrieb einen detaillierteren Blog hier :)

Chunkyguy
quelle
Ihr Link funktioniert nicht mehr.
i_am_jorf
0

Um das Beispiel von @ robbie-hanson zu erweitern ...

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;
}
JJD
quelle
0

Mein Weg ist so einfach:

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

Wenn der Singleton bereits initialisiert ist, wird der LOCK-Block nicht eingegeben. Die zweite Überprüfung, ob (! Initialisiert) ist, um sicherzustellen, dass es noch nicht initialisiert ist, wenn der aktuelle Thread das LOCK erhält.

TienDC
quelle
Es ist nicht klar, dass die Kennzeichnung initializedals volatileausreichend ist. Siehe aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf
0

Ich habe nicht alle Lösungen durchgelesen. Verzeihen Sie also, ob dieser Code redundant ist.

Dies ist meiner Meinung nach die thread-sicherste Implementierung.

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

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

    return sharedResourcesObj;
}
Zolt
quelle
-4

Normalerweise verwende ich Code, der dem in Ben Hoffsteins Antwort (die ich auch aus Wikipedia erhalten habe) in etwa ähnlich ist. Ich benutze es aus den Gründen, die Chris Hanson in seinem Kommentar angegeben hat.

Manchmal muss ich jedoch einen Singleton in eine NIB einfügen, und in diesem Fall verwende ich Folgendes:

@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

Ich überlasse die Implementierung von -retain(usw.) dem Leser, obwohl der obige Code alles ist, was Sie in einer Müllsammelumgebung benötigen.

Gregory Higley
quelle
2
Ihr Code ist nicht threadsicher. Es wird synchronisiert in der Alloc-Methode verwendet, jedoch nicht in der Init-Methode. Die Überprüfung des initialisierten Bools ist nicht threadsicher.
Mecki
-5

Die akzeptierte Antwort ist zwar kompiliert, aber falsch.

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

Gemäß Apple-Dokumentation:

... Sie können einen ähnlichen Ansatz verwenden, um die Klassenmethoden der zugeordneten Klasse zu synchronisieren, indem Sie das Class-Objekt anstelle von self verwenden.

Selbst wenn die Verwendung von Self funktioniert, sollte dies nicht der Fall sein, und dies scheint mir ein Fehler beim Kopieren und Einfügen zu sein. Die korrekte Implementierung für eine Klassenfactory-Methode wäre:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}
gelöschter Benutzer
quelle
6
Selbst sicherlich tut es Klassenbereich existieren. Es bezieht sich auf die Klasse anstelle der Instanz der Klasse. Klassen sind (meistens) erstklassige Objekte.
schwa
Warum setzen Sie @synchroninzed WITHIN in eine Methode?
user4951
1
Wie schwa bereits sagte, self befindet sich das Klassenobjekt innerhalb einer Klassenmethode. In meinem Kommentar finden Sie einen Ausschnitt, der dies demonstriert.
Jscs
selfvorhanden, aber wenn Sie es als übergebenen Bezeichner verwenden, @synchronizedwird der Zugriff auf die Methoden der Instanz synchronisiert. Wie @ user490696 hervorhebt, gibt es Fälle (wie Singletons), in denen die Verwendung des Klassenobjekts vorzuziehen ist. Aus dem Obj-C Programmierhandbuch: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