333 Stimmen

Wie sollte mein Objective-C-Singleton aussehen?

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?

12voto

lorean Punkte 2140

Bearbeiten: Diese Implementierung ist mit ARC überholt. Bitte werfen Sie einen Blick auf Wie kann ich ein Objective-C-Singleton implementieren, das mit ARC kompatibel ist? für eine korrekte Umsetzung.

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

+ (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. Denn Unterklassen rufen das Initialize standardmäßig auf. Es gibt einen nicht offensichtlichen Fall, in dem Unterklassen indirekt durch KVO erstellt werden können. Wenn Sie nämlich 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 erstellen, was zu einer zweiten Auslösung von +initialize .

Sie denken vielleicht, dass Sie in Ihrem init-Block implizit auf doppelte Initialisierung prüfen sollten:

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

Aber Sie werden sich selbst in den Fuß schießen; oder schlimmer noch, einem anderen Entwickler die Gelegenheit 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 Umsetzung

@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 Assertion-Makro; oder einfach NSAssert).

10voto

Matthieu Cormier Punkte 2155

Eine ausführliche Erklärung des Singleton-Makros finden Sie im Blog Cocoa With Love

http://cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html .

9voto

Ich habe eine interessante Variante von sharedInstance, die thread-sicher ist, aber nicht nach der Initialisierung sperren. Ich bin mir noch nicht sicher genug, um die obere Antwort wie gewünscht zu ändern, aber ich stelle 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;
}

6voto

quellish Punkte 20902

Kurze Antwort: Fabelhaft.

Lange Antwort: 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 dispatch/once.h Kopfzeile um zu verstehen, was vor sich geht. In diesem Fall sind die Kommentare in der Kopfzeile besser geeignet als die Dokumentation oder die Manpage.

5voto

obscenum Punkte 51

Ich habe Singleton in eine Klasse integriert, 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 für eine Klasse, die Sie zu einem Singleton machen wollen.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Die einzige Einschränkung der Singleton-Klasse ist, dass es sich um eine NSObject-Unterklasse handelt. Aber die meiste Zeit verwende ich Singletons in meinem Code sind sie in der Tat NSObject Unterklassen, so dass diese Klasse wirklich erleichtern mein Leben und machen Code sauberer.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X