Erstellen Sie einen Singleton mit GCDs dispatch_once in Objective-C

341

Wenn Sie auf iOS 4.0 oder höher abzielen können

Ist es mit GCD der beste Weg, Singleton in Objective-C (thread-sicher) zu erstellen?

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
Ryan
quelle
2
Gibt es eine Möglichkeit zu verhindern, dass Benutzer der Klasse alloc / copy aufrufen?
Nicolas Miari
3
dispatch_once_t und dispatch_once scheinen in 4.0 eingeführt worden zu sein, nicht in 4.1 (siehe: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn
1
Diese Methode wird problematisch, wenn init die Verwendung des Singleton-Objekts erfordert. Der Code von Matt Gallagher hat mehr als ein paar Mal für mich funktioniert. cocoawithlove.com/2008/11/...
greg
1
Ich weiß, dass es in diesem Beispiel keine Rolle spielt. aber warum benutzen die Leute nicht mehr "neu"? dispatch_once (& einmal, ^ {sharedInstance = [self new];} sieht nur ein bisschen ordentlicher aus. Es entspricht alloc + init.
Chris Hatton
3
Stellen Sie sicher, dass Sie den Rückgabetyp verwenden instancetype. Die Code-Vervollständigung ist viel besser, wenn Sie diese anstelle von verwenden id.
Herr Rogers

Antworten:

215

Dies ist eine absolut akzeptable und threadsichere Methode, um eine Instanz Ihrer Klasse zu erstellen. Es kann technisch gesehen kein "Singleton" sein (da es immer nur eines dieser Objekte geben kann), aber solange Sie nur die [Foo sharedFoo]Methode verwenden, um auf das Objekt zuzugreifen, ist dies gut genug.

Dave DeLong
quelle
4
Wie veröffentlichen Sie es jedoch?
Samvermette
65
@samvermette du nicht. Der Sinn eines Singletons ist, dass es immer existieren wird. Daher geben Sie es nicht frei und der Speicher wird beim Beenden des Prozesses wiederhergestellt.
Dave DeLong
6
@ Dave DeLong: Meiner Meinung nach ist der Zweck, Singleton zu haben, nicht die Gewissheit seiner Unsterblichkeit, sondern die Gewissheit, dass wir eine Instanz haben. Was ist, wenn dieser Singleton ein Semaphor dekrementiert? Man kann nicht einfach willkürlich sagen, dass es immer existieren wird.
Jacekmigacz
4
@hooleyhoop Ja, in seiner Dokumentation . "Wenn diese Funktion gleichzeitig von mehreren Threads aufgerufen wird, wartet sie synchron, bis der Block abgeschlossen ist."
Kevin
3
@ WalterMartinVargas-Pena die starke Referenz wird von der statischen Variablen gehalten
Dave DeLong
36

Instanztyp

instancetypeist nur eine der vielen Spracherweiterungen Objective-C, mit jeder neuen Version werden weitere hinzugefügt.

Wisse es, liebe es.

Nehmen Sie als Beispiel, wie Sie durch die Beachtung der Details auf niedriger Ebene Einblicke in leistungsstarke neue Möglichkeiten zur Transformation von Objective-C erhalten.

Siehe hier: Instanztyp


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}
Zelko
quelle
4
toller Tipp, danke! instancetype ist ein kontextbezogenes Schlüsselwort, das als Ergebnistyp verwendet werden kann, um zu signalisieren, dass eine Methode einen verwandten Ergebnistyp zurückgibt. ... Mit instancetype leitet der Compiler den Typ korrekt ab.
Fattie
1
Mir ist nicht klar, was die beiden Schnipsel hier bedeuten. Sind sie einander gleichwertig? Eins ist dem anderen vorzuziehen? Wäre schön, wenn der Autor dazu ein paar Erklärungen hinzufügen kann.
Galactica
33

MySingleton.h

@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

MySingleton.m

@implementation MySingleton

+(instancetype)sharedInstance {
    static dispatch_once_t pred;
    static id shared = nil;
    dispatch_once(&pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

-(instancetype)initUniqueInstance {
    return [super init];
}

@end
Sergey Petruk
quelle
Wie ist der Init nicht verfügbar? Ist es nicht zumindest für einen verfügbar init?
Honig
2
Singleton sollte nur einen Zugangspunkt haben. Und dieser Punkt ist sharedInstance. Wenn wir eine init-Methode in der * .h-Datei haben, können Sie eine weitere Singleton-Instanz erstellen. Dies widerspricht der Definition eines Singletons.
Sergey Petruk
1
@ asma22 __attribute __ ((nicht verfügbar ()) stellt diese Methoden nicht zur Verfügung. Wenn ein anderer Programmierer eine als nicht verfügbar gekennzeichnete Methode verwenden möchte, wird eine Fehlermeldung
angezeigt
1
Ich verstehe und bin froh, dass ich etwas Neues gelernt habe, nichts Falsches an Ihrer Antwort, kann für Neulinge etwas verwirrend sein ...
Honey
1
Dies funktioniert nur MySingletonzum Beispiel in MySingleton.mIch rufe an[super alloc]
Sergey Petruk
6

Sie können vermeiden, dass die Klasse zugewiesen wird, indem Sie die Zuweisungsmethode überschreiben.

@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}
i-Entwickler
quelle
1
Dies beantwortet meine Frage in den Kommentaren oben. Nicht dass ich so sehr auf defensive Programmierung stehe, aber ...
Nicolas Miari
5

Dave hat recht, das ist vollkommen in Ordnung. In den Apple-Dokumenten zum Erstellen eines Singletons finden Sie Tipps zur Implementierung einiger anderer Methoden, um sicherzustellen, dass nur eine erstellt werden kann, wenn Klassen die sharedFoo-Methode NICHT verwenden.

Christian
quelle
8
eh ... das ist nicht das beste Beispiel für die Erstellung eines Singletons. Das Überschreiben der Speicherverwaltungsmethoden ist nicht erforderlich.
Dave DeLong
19
Dies ist mit ARC völlig ungültig.
Logancautrell
Das zitierte Dokument wurde inzwischen eingestellt. Außerdem sind Antworten, bei denen es sich ausschließlich um Links zu externen Inhalten handelt, im Allgemeinen schlechte SO-Antworten. Zumindest ein Auszug relevanter Teile in Ihrer Antwort. Kümmere dich hier nicht darum, es sei denn, du willst den alten Weg für die Nachwelt retten.
Toolbear
4

Wenn Sie sicherstellen möchten, dass [[MyClass alloc] init] dasselbe Objekt wie sharedInstance zurückgibt (meiner Meinung nach nicht erforderlich, aber einige Leute möchten es), können Sie dies sehr einfach und sicher mit einem zweiten dispatch_once tun:

- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here. 
        sharedInstance = self;
    });

    return sharedInstance;
}

Dadurch kann jede Kombination von [[MyClass alloc] init] und [MyClass sharedInstance] dasselbe Objekt zurückgeben. [MyClass sharedInstance] wäre nur ein bisschen effizienter. So funktioniert es: [MyClass sharedInstance] ruft [[MyClass alloc] init] einmal auf. Andere Codes könnten es auch beliebig oft aufrufen. Der erste Aufrufer von init führt die "normale" Initialisierung durch und speichert das Singleton-Objekt in der init-Methode. Bei späteren Aufrufen von init wird die zurückgegebene Zuordnung vollständig ignoriert und dieselbe sharedInstance zurückgegeben. Das Ergebnis der Zuweisung wird freigegeben.

Die + sharedInstance-Methode funktioniert wie immer. Wenn es nicht der erste Aufrufer ist, der [[MyClass alloc] init] aufruft, ist das Ergebnis von init nicht das Ergebnis des Alloc-Aufrufs, aber das ist in Ordnung.

gnasher729
quelle
2

Sie fragen, ob dies der "beste Weg ist, um Singleton zu erstellen".

Ein paar Gedanken:

  1. Erstens, ja, dies ist eine thread-sichere Lösung. Dieses dispatch_onceMuster ist die moderne, thread-sichere Methode zum Generieren von Singletons in Objective-C. Keine Sorge da.

  2. Sie haben jedoch gefragt, ob dies der "beste" Weg ist, dies zu tun. Man sollte jedoch anerkennen, dass das instancetypeund[[self alloc] init] in Verbindung mit Singletons möglicherweise irreführend ist.

    Der Vorteil von instancetypeist, dass es eine eindeutige Methode ist, zu erklären, dass die Klasse in Unterklassen unterteilt werden kann, ohne auf eine Art von Klasse zurückzugreifenid , wie wir es früher getan haben.

    Aber das staticin dieser Methode stellt Unterklassenherausforderungen. Was wäre, wenn ImageCacheund BlobCacheSingletons beide Unterklassen einer CacheOberklasse wären, ohne ihre eigene sharedCacheMethode zu implementieren ?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    Damit dies funktioniert, müssen Sie sicherstellen, dass Unterklassen ihre eigenen implementieren sharedInstance Methode (oder wie auch immer Sie sie für Ihre bestimmte Klasse nennen) .

    Unterm Strich sharedInstance sieht Ihr Original so aus, als würde es Unterklassen unterstützen, aber nicht. Wenn Sie Unterklassen unterstützen möchten, fügen Sie zumindest eine Dokumentation hinzu, die zukünftige Entwickler warnt, dass sie diese Methode überschreiben müssen.

  3. Für eine optimale Interoperabilität mit Swift möchten Sie dies wahrscheinlich als eine Eigenschaft definieren, nicht als eine Klassenmethode, z. B.:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    Dann können Sie einen Getter für diese Eigenschaft schreiben (die Implementierung würde das von dispatch_onceIhnen vorgeschlagene Muster verwenden):

    + (Foo *)sharedFoo { ... }

    Dies hat den Vorteil, dass ein Swift-Benutzer, wenn er es verwendet, Folgendes tun würde:

    let foo = Foo.shared

    Beachten Sie, dass dies nicht der Fall ist (), da wir es als Eigenschaft implementiert haben. Ab Swift 3 wird im Allgemeinen auf Singletons zugegriffen. Die Definition als Eigenschaft erleichtert die Interoperabilität.

    Abgesehen davon, wenn Sie sich ansehen, wie Apple ihre Singletons definiert, ist dies das Muster, das sie übernommen haben, z. B. ist ihr NSURLSessionSingleton wie folgt definiert:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Eine weitere, sehr geringfügige Überlegung zur Swift-Interoperabilität war der Name des Singletons. Es ist am besten, wenn Sie den Namen des Typs einfügen und nicht sharedInstance. Wenn die Klasse beispielsweise war Foo, können Sie die Singleton-Eigenschaft als definieren sharedFoo. Oder wenn die Klasse war DatabaseManager, können Sie die Eigenschaft aufrufen sharedManager. Dann könnten Swift-Benutzer Folgendes tun:

    let foo = Foo.shared
    let manager = DatabaseManager.shared

    Wenn Sie wirklich verwenden möchten sharedInstance, können Sie den Swift-Namen jederzeit deklarieren , wenn Sie möchten:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    Wenn Sie Objective-C-Code schreiben, sollten wir nicht zulassen, dass die Swift-Interoperabilität andere Entwurfsüberlegungen überwiegt. Wenn wir jedoch Code schreiben können, der beide Sprachen ordnungsgemäß unterstützt, ist dies vorzuziehen.

  5. Ich stimme anderen zu, die darauf hinweisen, dass, wenn Sie möchten, dass dies ein echter Singleton ist, bei dem Entwickler ihre eigenen Instanzen nicht (versehentlich) instanziieren können / sollten, das unavailableQualifikationsmerkmal aktiviert ist initund newumsichtig ist.

rauben
quelle
0

So erstellen Sie einen thread-sicheren Singleton:

@interface SomeManager : NSObject
+ (id)sharedManager;
@end

/* thread safe */
@implementation SomeManager

static id sharedManager = nil;

+ (void)initialize {
    if (self == [SomeManager class]) {
        sharedManager = [[self alloc] init];
    }
}

+ (id)sharedManager {
    return sharedManager;
}
@end

und dieser Blog erklärt Singleton sehr gut Singletons in Objc / Kakao

Hancock_Xu
quelle
Sie verlinken auf einen sehr alten Artikel, während OP nach Merkmalen für die modernste Implementierung fragt.
Vikingosegundo
1
Die Frage bezieht sich auf eine bestimmte Implementierung. Sie veröffentlichen einfach eine andere Implementierung. Deshalb versuchen Sie nicht einmal, die Frage zu beantworten.
Vikingosegundo
1
@vikingosegundo Der Fragesteller fragt, ob die GCD der beste Weg ist, um einen Thread-sicheren Singleton zu erstellen. Meine Antwort gibt eine andere Wahl. Irgendein Fehler?
Hancock_Xu
Der Fragesteller fragt, ob eine bestimmte Implementierung threadsicher ist. er fragt nicht nach Optionen.
Vikingosegundo
0
//Create Singleton  
  +( instancetype )defaultDBManager
    {

        static dispatch_once_t onceToken = 0;
        __strong static id _sharedObject = nil;

        dispatch_once(&onceToken, ^{
            _sharedObject = [[self alloc] init];
        });

        return _sharedObject;
    }


//In it method
-(instancetype)init
{
    self = [super init];
  if(self)
     {
   //Do your custom initialization
     }
     return self;
}
Rohit Kashyap
quelle
0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
Nayab Muhammad
quelle