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.
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.
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:
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.
Okay, lassen Sie mich erklären, wie das funktioniert:
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.
Langsamer Fall: Wenn sharedInstancenicht vorhanden, wird eine Instanz zugewiesen und mithilfe von Compare And Swap ('CAS') in diese kopiert.
Stritten Fall: Wenn zwei Threads beide Versuch Aufruf sharedInstancezur gleichen Zeit undsharedInstance 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.
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.
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.
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:
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 cachesstaticvolatileALBackendRequestManager*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;}
+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.
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.
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.
@interfaceMySingleton:NSObject{}+(MySingleton*)sharedManager;@end@implementationMySingletonstaticMySingleton*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 herereturn self;}@end
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.
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:
@implementationMYSingletonstaticMYSingleton* sharedInstance = nil;+( id )sharedInstance {@synchronized([MYSingletonclass]){if( sharedInstance == nil )
sharedInstance =[[MYSingleton alloc ] init ];}return sharedInstance;}+( id )allocWithZone:(NSZone*)zone {@synchronized([MYSingletonclass]){if( sharedInstance == nil )
sharedInstance =[ super allocWithZone:zone ];}return sharedInstance;}-( id )init {@synchronized([MYSingletonclass]){
self =[ super init ];if( self != nil ){// Insert initialization code here}return self;}}@end
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 selfbefindet 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; }
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.
*/#defineSingletonInterface(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.
*/#defineSingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+(void)initialize \
{ \
static BOOL initialized = NO; \
if(!initialized) \
{ \
initialized = YES; \
__ ## NAME = [[TYPE alloc] init]; \} \
} \
\
\
+(TYPE *)NAME \
{ \
return __ ## NAME; \}
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.
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!
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.
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:
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:
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, selfbefindet 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.
Antworten:
Eine andere Möglichkeit ist die Verwendung der
+(void)initialize
Methode. Aus der Dokumentation:Sie könnten also etwas Ähnliches tun:
quelle
+initialize
ihre Oberklassen nicht überschreiben , wird die Implementierung aufgerufen, wenn die Unterklasse zum ersten Mal verwendet wird.release
Methode überschreiben und leer machen. :)[Quelle]
quelle
MySingleton *s = [[MySingelton alloc] init];
Pro Objective-C Design Patterns for iOS
und 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 *)zone
die insharedSingleton
Gemäß meiner anderen Antwort unten denke ich, dass Sie tun sollten:
quelle
Da Kendall einen threadsicheren Singleton veröffentlicht hat, der versucht, Sperrkosten zu vermeiden, dachte ich, ich würde auch einen werfen:
Okay, lassen Sie mich erklären, wie das funktioniert:
Schneller Fall: In der normalen Ausführung
sharedInstance
wurde bereits festgelegt, sodass diewhile
Schleife niemals ausgeführt wird und die Funktion nach einfachem Testen der Existenz der Variablen zurückkehrt.Langsamer Fall: Wenn
sharedInstance
nicht vorhanden, wird eine Instanz zugewiesen und mithilfe von Compare And Swap ('CAS') in diese kopiert.Stritten Fall: Wenn zwei Threads beide Versuch Aufruf
sharedInstance
zur gleichen Zeit undsharedInstance
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ücksharedInstance
. Die SingleOSAtomicCompareAndSwapPtrBarrier
fungiert sowohl als Schreibbarriere für den Einstellungs-Thread als auch als Lesebarriere für den Test-Thread.quelle
init
Methode in Ihrem Ansatz tun?sharedInstance
Ich glaube, es ist keine gute Idee, bei der Initialisierung eine Ausnahme auszulösen. Was ist dann zu tun, um zu verhindern, dass Benutzerinit
mehrmals direkt anrufen ?quelle
[[self alloc] init]
auf sharedInst zuweisen.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.
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:
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:
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.
TL; DR, hier ist meine Implementierung
(Ersetzen Sie ZAssert durch unser eigenes Assertionsmakro oder nur durch NSAssert.)
quelle
Eine ausführliche Erklärung des Singleton-Makrocodes finden Sie im Blog Cocoa With Love
http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html .
quelle
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:
quelle
class_replaceMethod
um michsharedInstance
in einen Klon von zu verwandelnsimpleSharedInstance
. Auf diese Weise müssten Sie sich nie wieder Sorgen machen, ein@synchronized
Schloss zu erwerben .Kurze Antwort: Fabelhaft.
Lange Antwort: So etwas wie ....
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.
quelle
Ich habe Singleton in eine Klasse gerollt, damit andere Klassen Singleton-Eigenschaften erben können.
Singleton.h:
Singleton.m:
Und hier ist ein Beispiel für eine Klasse, die Sie Singleton werden möchten.
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.
quelle
@synchronized
schrecklich langsam ist und vermieden werden sollte.Dies funktioniert auch in einer Umgebung ohne Müllabfuhr.
quelle
Sollte dies nicht threadsicher sein und das teure Sperren nach dem ersten Anruf vermeiden?
quelle
Hier ist ein Makro , das ich zusammengestellt habe:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
Es basiert auf der Arbeit von Matt Gallagher. Die Implementierung wurde jedoch geändert, um die hier von Dave MacLachlan von Google beschriebene Methode Swizzling zu verwenden .
Ich freue mich über Kommentare / Beiträge.
quelle
Wie wäre es mit
So vermeiden Sie die Synchronisationskosten nach der Initialisierung?
quelle
Eine ausführliche Beschreibung des Singleton-Musters in Objective-C finden Sie hier:
Verwenden des Singleton-Musters in Objective-C
quelle
KLSingleton
quelle
NSLog(@"initialize: %@", NSStringFromClass([self class]));
der+initialize
Methode hinzufügen , können Sie überprüfen, ob die Klassen nur einmal initialisiert werden.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:
quelle
self
Objekt in einer Klassenmethode nicht vorhanden ist. Die Laufzeit bestimmt, welche Methodenimplementierung aufgerufen werden soll, basierend auf genau demselben Wert, den sie fürself
jede Methode (Klasse oder Instanz) bereitstellt .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; }
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 :
quelle
quelle
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
Anwendungsbeispiel:
MyManager.h
MyManager.m
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.
quelle
Mit Objective C-Klassenmethoden können wir einfach vermeiden, das Singleton-Muster auf die übliche Weise zu verwenden:
zu:
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 :)
quelle
Um das Beispiel von @ robbie-hanson zu erweitern ...
quelle
Mein Weg ist so einfach:
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.
quelle
initialized
alsvolatile
ausreichend ist. Siehe aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .Ich habe nicht alle Lösungen durchgelesen. Verzeihen Sie also, ob dieser Code redundant ist.
Dies ist meiner Meinung nach die thread-sicherste Implementierung.
quelle
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:
Ich überlasse die Implementierung von
-retain
(usw.) dem Leser, obwohl der obige Code alles ist, was Sie in einer Müllsammelumgebung benötigen.quelle
Die akzeptierte Antwort ist zwar kompiliert, aber falsch.
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:
quelle
self
befindet sich das Klassenobjekt innerhalb einer Klassenmethode. In meinem Kommentar finden Sie einen Ausschnitt, der dies demonstriert.self
vorhanden, aber wenn Sie es als übergebenen Bezeichner verwenden,@synchronized
wird 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.