Wie sollte mein Objective-C-Singleton aussehen?[geschlossen]
-
02-07-2019 - |
Frage
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?
Lösung
Eine andere Möglichkeit besteht darin, das zu verwenden +(void)initialize
Methode.Aus der Dokumentation:
Die Laufzeit sendet
initialize
an jede Klasse in einem Programm genau einmal, kurz bevor die Klasse oder eine Klasse, die von ihr erbt, ihre erste Nachricht aus dem Programm gesendet wird.(Daher darf die Methode niemals aufgerufen werden, wenn die Klasse nicht verwendet wird.) Die Laufzeit sendet dieinitialize
Nachricht an Klassen auf threadsichere Weise senden.Oberklassen erhalten diese Nachricht vor ihren Unterklassen.
Sie könnten also etwa Folgendes tun:
static MySingleton *sharedSingleton;
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
initialized = YES;
sharedSingleton = [[MySingleton alloc] init];
}
}
Andere Tipps
@interface MySingleton : NSObject
{
}
+ (MySingleton *)sharedSingleton;
@end
@implementation MySingleton
+ (MySingleton *)sharedSingleton
{
static MySingleton *sharedSingleton;
@synchronized(self)
{
if (!sharedSingleton)
sharedSingleton = [[MySingleton alloc] init];
return sharedSingleton;
}
}
@end
Per meine andere Antwort unten, ich glaube, Sie tun sollten:
+ (id)sharedFoo
{
static dispatch_once_t once;
static MyFoo *sharedFoo;
dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
return sharedFoo;
}
Da Kendall geschrieben eine THREAD Singletons, die Kosten zu vermeiden versucht, Sperren, ich dachte, dass ich ein, so gut werfen würde:
#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:
-
Fast Fall: Im Normal hat Ausführung
sharedInstance
bereits eingestellt worden ist, so dass diewhile
Schleife nie ausgeführt und die Funktion kehrt nach nur für die Variable Existenz zu testen; -
Langsam Fall: Wenn
sharedInstance
nicht existiert, dann wird eine Instanz zugeordnet und in sie kopiert mit einer Vergleichs- und Auslagerungs ( 'CAS'); -
stritten Fall: Wenn zwei Threads versuchen beide
sharedInstance
zugleich zu nennen undsharedInstance
nicht zur gleichen Zeit vorhanden ist, dann wird sie neue Instanzen der Singleton initialisieren beide und Versuch es in der richtigen Position zu CAS. Unabhängig davon, welche man gewinnt das CAS sofort zurückkehrt, je nachdem, was man verliert Releases die Instanz es nur zugewiesen und gibt die (jetzt eingestellt)sharedInstance
. Die einzelneOSAtomicCompareAndSwapPtrBarrier
wirken sowohl als Schreibbarriere für die Einstellung Gewinde und eine Lesesperre von den Testgewinden.
static MyClass *sharedInst = nil; + (id)sharedInstance { @synchronize( self ) { if ( sharedInst == nil ) { /* sharedInst set up in init */ [[self alloc] init]; } } return sharedInst; } - (id)init { if ( sharedInst != nil ) { [NSException raise:NSInternalInconsistencyException format:@"[%@ %@] cannot be called; use +[%@ %@] instead"], NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class]), NSStringFromSelector(@selector(sharedInstance)"]; } else if ( self = [super init] ) { sharedInst = self; /* Whatever class specific here */ } return sharedInst; } /* These probably do nothing in a GC app. Keeps singleton as an actual singleton in a non CG app */ - (NSUInteger)retainCount { return NSUIntegerMax; } - (oneway void)release { } - (id)retain { return sharedInst; } - (id)autorelease { return sharedInst; }
Edit: Diese Implementierung mit ARC veraltete. Weitere Informationen finden sie unter Wie kann ich einen Objective-C Singleton implementieren, die mit ARC kompatibel ist? für die korrekte Umsetzung .
Alle Implementierungen von initialize ich in anderen Antworten gelesen habe einen gemeinsamen Fehler.
+ (void) initialize {
_instance = [[MySingletonClass alloc] init] // <----- Wrong!
}
+ (void) initialize {
if (self == [MySingletonClass class]){ // <----- Correct!
_instance = [[MySingletonClass alloc] init]
}
}
Die Apple-Dokumentation wird empfohlen, den Klassentyp in Ihrem initialize Block zu überprüfen. Da Subklassen rufen Sie die initialize standardmäßig. Es gibt einen nicht-offensichtlichen Fall, in dem Subklassen kann indirekt durch KVO erstellt werden. Denn wenn Sie die folgende Zeile in einer anderen Klasse hinzufügen:
[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]
Objective-C wird implizit eine Unterklasse von MySingletonClass schafft in einer zweiten Auslösung +initialize
zur Folge hat.
Sie können denken, dass Sie implizit für doppelte Initialisierung im Init-Block als solche überprüfen sollten:
- (id) init { <----- Wrong!
if (_instance != nil) {
// Some hack
}
else {
// Do stuff
}
return self;
}
Aber Sie werden sich in den Fuß schießen; oder noch schlimmer ein anderer Entwickler die Möglichkeit geben, sich selbst 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 ZAssert mit unserer eigenen Behauptung Makro oder nur NSAssert.)
Eine ausführliche Erklärung des Singleton-Makrocode ist auf dem Blog Cocoa With Love
http://cocoawithlove.com/2008/11 /singletons-appdelegates-and-top-level.html .
Ich habe eine interessante Variante sharedInstance, den Thread-sicher ist, aber nicht gesperrt nach der Initialisierung. Ich bin mir noch nicht sicher genug, um von der Top-Antwort zu ändern, wie gewünscht, aber ich präsentiere es für die weitere 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;
}
Kurze Antwort:. Fabulous
Lange Antwort: So etwas ....
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
Seien Sie sicher, dass der Versand lesen / once.h Header verstehen Was ist los. In diesem Fall sind die Header Kommentare mehr anwendbar ist als die docs oder man-Seite.
Ich habe Singleton in eine Klasse gerollt, so dass 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 einer Klasse, dass Sie Singletons werden wollen.
#import "Singleton.h"
@interface SomeClass : Singleton {
}
@end
@implementation SomeClass
DEFINE_SHARED_INSTANCE;
@end
Die einzige Einschränkung über Singleton Klasse, ist, dass es NSObject Unterklasse ist. Aber die meiste Zeit ich Singletons in meinem Code verwenden, sie sind in der Tat NSObject Unterklassen, so dass diese Klasse wirklich mein Leben erleichtern und Code sauberer machen.
Dies funktioniert in einer nicht-Müll gesammelt Umgebung auch.
@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
Sollten diese werden nicht Thread-sicher und vermeiden die teure Verriegelung nach dem ersten Aufruf?
+ (MySingleton*)sharedInstance
{
if (sharedInstance == nil) {
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[MySingleton alloc] init];
}
}
}
return (MySingleton *)sharedInstance;
}
Hier ist ein Makro , die ich zusammen:
http://github.com/cjhanson/Objective-C-Optimized-Singleton
Es basiert auf die Arbeit hier von Matt Gallagher Aber die Änderung der Implementierung zu verwenden Methode Swizzling wie hier beschrieben von Dave MacLachlan von Google .
Ich begrüße Kommentare / Beiträge.
Wie wäre es
static MyClass *gInstance = NULL;
+ (MyClass *)instance
{
if (gInstance == NULL) {
@synchronized(self)
{
if (gInstance == NULL)
gInstance = [[self alloc] init];
}
}
return(gInstance);
}
So können Sie die Synchronisation Kosten nach der Initialisierung vermeiden?
Für eine eingehende Diskussion des Singletonmuster in Objective-C, schau mal hier:
KLSingleton ist:
- Subclassible (zum n-ten Grades)
- ARC kompatibel
- Sicher mit
alloc
undinit
- Loaded lazily
- Thread-safe
- Sperren frei (Anwendungen + initialisieren, nicht @synchronize)
- Makro-frei
- Swizzle frei
- Einfache
Sie wollen nicht auf sich selbst synchronisieren ... Da das Selbst Objekt existiert noch nicht! Sie enden auf einem temporären ID-Wert nach oben zu verriegeln. Sie wollen sicherstellen, dass niemand sonst Klassenmethoden ausführen können (sharedInstance, alloc, allocWithZone :, etc.), so müssen Sie auf das Klassenobjekt synchronisieren statt:
@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
Ich wollte nur diese verlassen hier, damit ich es nicht verlieren. Der Vorteil dieser ist, dass es in Interface, verwendbar ist, was ein großer Vorteil ist. Dies wird von einer anderen Frage genommen, die ich fragte :
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;
}
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
Ich weiß, dass es eine Menge Kommentare zu dieser „Frage“ ist, aber ich sehe nicht viele Leute mit einem Makro was darauf hindeutet, das Singleton zu definieren. Es ist so ein gemeinsames Muster und ein Makro vereinfacht die Singleton.
Hier sind die Makros ich auf mehreren ObjC Implementierungen auf Basis habe 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 Interface Makro, wenn es fast leer ist? Code Kohärenz zwischen den Kopf- und Code-Dateien; Wartbarkeit, falls Sie weitere automatische Verfahren oder es hinzufügen drehen.
Ich bin mit der Methode initialize die Singleton zu erstellen, wie hier in der populärste Antwort verwendet wird (zum Zeitpunkt des Schreibens).
Mit Objective C-Klasse Methoden können wir vermeiden, nur das Singletonmuster mit der üblichen Weise aus:
[[Librarian sharedInstance] openLibrary]
zu:
[Librarian openLibrary]
durch die Klasse innerhalb einer anderen Klasse Verpackung, die gerade hat Klassenmethoden , auf diese Weise gibt es keine Möglichkeit der doppelten Instanzen zufällig zu schaffen, da wir keine Instanz erstellen!
Ich schrieb einen detaillierteren Blog hier :)
Um das Beispiel von @ erweitert robbie-hanson ...
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;
}
Mein Weg ist einfach so:
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 die Singleton bereits initialisiert wird, wird der LOCK-Block nicht eingegeben werden. Die zweite Prüfung if (! Initialisiert) ist sicherzustellen, dass es noch nicht initialisiert wird, wenn der aktuelle Thread die Sperre erhält.
Ich habe nicht durch alle Lösungen lesen, so vergeben, wenn dieser Code redundant ist.
Dies ist die Threadsicherheit Umsetzung meiner Meinung nach.
+(SingletonObject *) sharedManager
{
static SingletonObject * sharedResourcesObj = nil;
@synchronized(self)
{
if (!sharedResourcesObj)
{
sharedResourcesObj = [[SingletonObject alloc] init];
}
}
return sharedResourcesObj;
}
Normalerweise verwende ich Code in etwa ähnlich wie bei Ben Hoffstein Antwort (die ich auch aus Wikipedia bekam). Ich benutze es für die von Chris Hanson in seinem Kommentar genannten Gründe.
Aber manchmal habe ich eine Notwendigkeit, einen Singleton in eine NIB zu platzieren, und in diesem Fall verwende ich die folgende:
@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 lasse die Umsetzung von -retain
(etc.) an den Leser, obwohl der obige Code ist alles, was Sie in einer Garbage Collection-Umgebung benötigen.
Die akzeptierte Antwort, obwohl es kompiliert, ist falsch.
+ (MySingleton*)sharedInstance
{
@synchronized(self) <-------- self does not exist at class scope
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
Pro Apple-Dokumentation:
... Sie können einen ähnlichen Ansatz nehmen die Klassenmethoden der zugehörigen Klasse, mit dem Klasse-Objekt anstelle selbst.
synchronisierenAuch wenn mit selbst funktioniert, soll es nicht, und das sieht aus wie ein Kopieren und Einfügen Fehler zu mir. Die korrekte Implementierung für eine Klasse Factory-Methode wäre:
+ (MySingleton*)getInstance
{
@synchronized([MySingleton class])
{
if (sharedInstance == nil)
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}