596 Stimmen

Verwendung eines Dispatch-Once-Singleton-Modells in Swift

Ich versuche, ein geeignetes Singleton-Modell für die Verwendung in Swift herauszufinden. Bisher konnte ich ein nicht threadsicheres Modell zum Laufen bringen, wie folgt:

class var sharedInstance: TPScopeManager {
    get {
        struct Static {
            static var instance: TPScopeManager? = nil
        }

        if !Static.instance {
            Static.instance = TPScopeManager()
        }

        return Static.instance!
    }
}

Das Einwickeln der Singleton-Instanz in der Static-Struktur sollte eine einzelne Instanz ermöglichen, die nicht mit anderen Singleton-Instanzen kollidiert, ohne komplizierte Namenskonventionen anwenden zu müssen, und es sollte die Dinge ziemlich privat halten. Offensichtlich ist dieses Modell jedoch nicht threadsicher. Also habe ich versucht, dispatch_once zum Ganzen hinzuzufügen:

class var sharedInstance: TPScopeManager {
    get {
        struct Static {
            static var instance: TPScopeManager? = nil
            static var token: dispatch_once_t = 0
        }

        dispatch_once(Static.token) { Static.instance = TPScopeManager() }

        return Static.instance!
    }
}

Aber ich bekomme einen Compilerfehler in der dispatch_once-Zeile:

Kann den Typ des Ausdrucks 'Void' nicht in den Typ '()' konvertieren

Ich habe schon einige verschiedene Varianten der Syntax ausprobiert, aber sie scheinen alle die gleichen Ergebnisse zu haben:

dispatch_once(Static.token, { Static.instance = TPScopeManager() })

Was ist die richtige Verwendung von dispatch_once mit Swift? Ich dachte zunächst, das Problem sei mit dem Block aufgrund der () in der Fehlermeldung, aber je mehr ich darüber nachdenke, desto mehr glaube ich, dass es darauf ankommt, den dispatch_once_t richtig zu definieren.

728voto

hpique Punkte 115846

tl;dr: Verwenden Sie den Klassenkonstanten-Ansatz, wenn Sie Swift 1.2 oder höher verwenden, und den verschachtelten Struct-Ansatz, wenn Sie frühere Versionen unterstützen müssen.

Nach meiner Erfahrung mit Swift gibt es drei Ansätze, um das Singleton-Muster zu implementieren, die Lazy-Initialisierung und Thread-Sicherheit unterstützen.

Klassenkonstante

class Singleton  {
   static let sharedInstance = Singleton()
}

Dieser Ansatz unterstützt die Lazy-Initialisierung, weil Swift Klassenkonstanten (und Variablen) träge initialisiert und durch die Definition von let thread-sicher ist. Dies ist nun der offiziell empfohlene Weg, um ein Singleton zu instanziieren.

Klassenkonstanten wurden in Swift 1.2 eingeführt. Wenn Sie eine frühere Version von Swift unterstützen müssen, verwenden Sie den unten stehenden verschachtelten Struct-Ansatz oder eine globale Konstante.

Verschachtelter Struct

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static let instance: Singleton = Singleton()
        }
        return Static.instance
    }
}

Hier verwenden wir die statische Konstante eines verschachtelten Structs als Klassenkonstante. Dies ist ein Workaround für das Fehlen von statischen Klassenkonstanten in Swift 1.1 und früher und funktioniert immer noch als Workaround für das Fehlen von statischen Konstanten und Variablen in Funktionen.

dispatch_once

Der traditionelle Objective-C-Ansatz, der nach Swift übertragen wurde. Ich bin ziemlich sicher, dass es keinen Vorteil gegenüber dem verschachtelten Struct-Ansatz gibt, aber ich stelle es hier trotzdem vor, da ich die Unterschiede in der Syntax interessant finde.

class Singleton {
    class var sharedInstance: Singleton {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: Singleton? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = Singleton()
        }
        return Static.instance!
    }
}

Sehen Sie dieses GitHub Projekt für Unittests.

176voto

David Berry Punkte 40327

Da Apple jetzt klargestellt hat, dass statische Strukturvariablen sowohl faul als auch in dispatch_once eingewickelt initialisiert werden (siehe die Notiz am Ende des Beitrags), denke ich, dass meine endgültige Lösung sein wird:

class WithSingleton {
    class var sharedInstance: WithSingleton {
        struct Singleton {
            static let instance = WithSingleton()
        }

        return Singleton.instance
    }
}

Dies nutzt die automatische, faule, threadsichere Initialisierung statischer Strukturelemente aus und verbirgt die tatsächliche Implementierung sicher vor dem Verbraucher, hält alles kompakt für die Lesbarkeit kompakt und eliminiert eine sichtbare globale Variable.

Apple hat klargestellt, dass faule Initialisierer threadsicher sind, sodass dispatch_once oder ähnliche Schutzmaßnahmen nicht erforderlich sind

Der faule Initialisierer für eine globale Variablen (auch für statische Elemente von Strukturen und Aufzählungen) wird beim ersten Zugriff auf die globale Variablen ausgeführt und wird als dispatch_once gestartet, um sicherzustellen, dass die Initialisierung atomar ist. Dies ermöglicht eine coole Möglichkeit, dispatch_once in Ihrem Code zu verwenden: Deklarieren Sie einfach eine globale Variable mit einem Initialisierer und markieren Sie sie als privat.

Von hier

164voto

Jack Punkte 16589

Für Swift 1.2 und darüber hinaus:

class Singleton  {
   static let sharedInstance = Singleton()
}

Mit einem Korrektheitsnachweis (alle Kredit geht here), gibt es jetzt kaum noch einen Grund, die früheren Methoden für Singletons zu verwenden.

Aktualisierung: Dies ist nun der offizielle Weg, um Singletons zu definieren, wie es in den offiziellen Dokumenten beschrieben wird!

In Bezug auf die Verwendung von static vs class. static sollte verwendet werden, auch wenn class Variablen verfügbar sind. Singletons sollen nicht untergeordnet werden, da dies zu mehreren Instanzen des Basissingletons führen würde. Die Verwendung von static setzt dies auf eine schöne, Swifty Weise durch.

Für Swift 1.0 und 1.1:

Mit den jüngsten Änderungen in Swift, hauptsächlich neuen Methoden zur Zugriffssteuerung, neige ich nun dazu, den saubereren Weg der Verwendung einer globalen Variable für Singletons zu bevorzugen.

private let _singletonInstance = SingletonClass()
class SingletonClass {
  class var sharedInstance: SingletonClass {
    return _singletonInstance
  }
}

Wie im Swift-Blogartikel here erwähnt:

Der Lazy-Initializer für eine globale Variable (auch für statische Elemente von Strukturen und Aufzählungen) wird beim ersten Zugriff auf diese globale Variante ausgeführt, und wird mit dispatch_once gestartet, um sicherzustellen, dass die Initialisierung atomar ist. Dies ermöglicht eine coole Möglichkeit, dispatch_once in Ihrem Code zu verwenden: einfach eine globale Variable mit einem Initialisierer deklarieren und sie als privat markieren.

Diese Art der Erstellung eines Singletons ist threadsicher, schnell, faul und auch kostenlos in ObjC übertragen.

47voto

Florian Punkte 5296

Swift 1.2 oder später unterstützt jetzt statische Variablen/Konstanten in Klassen. So können Sie einfach eine statische Konstante verwenden:

class MySingleton {

    static let sharedMySingleton = MySingleton()

    private init() {
        // ...
    }
}

35voto

Kris Gellci Punkte 9439

Es gibt einen besseren Weg, um es zu tun. Sie können eine globale Variable in Ihrer Klasse über der Klassendeklaration wie folgt deklarieren:

var tpScopeManagerSharedInstance = TPScopeManager()

Dies ruft einfach Ihr Standardinit oder ein beliebiges init auf und globale Variablen sind standardmäßig in Swift dispatch_once. Dann in der Klasse, auf die Sie verweisen möchten, machen Sie einfach dies:

var reference = tpScopeManagerSharedInstance
// oder Sie können einfach Eigenschaften abrufen und Methoden direkt aufrufen
tpScopeManagerSharedInstance.someMethod()

Also können Sie im Grunde den gesamten Block des Codes für die gemeinsame Instanz loswerden.

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