Pregunta Uso de un modelo de singleton dispatch_once en Swift


Estoy tratando de encontrar un modelo singleton apropiado para usar en Swift. Hasta ahora, he podido obtener un modelo que no sea seguro para subprocesos y que funciona como:

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

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

        return Static.instance!
    }
}

Envolver la instancia de singleton en la estructura estática debería permitir una única instancia que no colisione con instancias únicas sin complejos mapeos de nombres, y debería hacer que las cosas sean bastante privadas. Obviamente, sin embargo, este modelo no es seguro para subprocesos, así que traté de agregar dispatch_once a todo el asunto:

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!
    }
}

Pero obtengo un error de compilación en dispatch_once línea:

No se puede convertir el tipo de expresión 'Void' para escribir '()'

He intentado varias variantes diferentes de la sintaxis, pero todas parecen tener los mismos resultados:

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

¿Cuál es el uso apropiado de dispatch_once usando Swift? Inicialmente pensé que el problema era con el bloque debido a la () en el mensaje de error, pero cuanto más lo miro, más creo que puede ser una cuestión de obtener el dispatch_once_t correctamente definido.


532
2018-06-03 20:41


origen


Respuestas:


tl; dr: Usa el constante de clase enfoque si está utilizando Swift 1.2 o superior y el estructura anidada enfoque si necesita soportar versiones anteriores.

Según mi experiencia con Swift, existen tres enfoques para implementar el patrón Singleton que admite la inicialización lenta y la seguridad de subprocesos.

Constante de clase

class Singleton  {
   static let sharedInstance = Singleton()
}

Este enfoque admite la inicialización diferida porque Swift inicializa las constantes de clase (y las variables) y está protegido contra subprocesos por la definición de let. Esto es ahora manera oficialmente recomendada para crear una instancia de singleton.

Las constantes de clase se introdujeron en Swift 1.2. Si necesita admitir una versión anterior de Swift, utilice el siguiente método de estructura anidada o una constante global.

Estructura anidada

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

Aquí estamos usando la constante estática de una estructura anidada como una constante de clase. Esta es una solución para la falta de constantes de clases estáticas en Swift 1.1 y anteriores, y todavía funciona como una solución para la falta de constantes estáticas y variables en las funciones.

dispatch_once

El enfoque tradicional de Objective-C trasladado a Swift. Estoy bastante seguro de que no hay ninguna ventaja sobre el enfoque de estructura anidada, pero lo estoy poniendo aquí de todos modos, ya que las diferencias de sintaxis me parecen interesantes.

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!
    }
}

Mira esto GitHub proyecto para pruebas unitarias


668
2018-06-10 17:57



Dado que Apple ahora ha aclarado que las variables de estructura estáticas se inicializaron tanto como "perezosas" y envueltas en dispatch_once (vea la nota al final de la publicación), creo que mi solución final será:

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

        return Singleton.instance
    }
}

Esto aprovecha la inicialización automática, floja y segura de subprocesos de los elementos de estructura estáticos, oculta de manera segura la implementación real del consumidor, mantiene todo compartimentado de manera compacta para la legibilidad y elimina una variable global visible.

Apple ha aclarado que el inicializador lento es seguro para subprocesos, por lo que no es necesario dispatch_once o protecciones similares

El inicializador diferido de una variable global (también para miembros estáticos de estructuras y enumeraciones) se ejecuta la primera vez que se accede a global, y se inicia como dispatch_once para asegurarse de que la inicialización sea atómica. Esto permite una manera genial de usar dispatch_once en tu código: simplemente declara una variable global con un inicializador y márcala como privada.

De aquí


169
2018-06-03 20:55



Para Swift 1.2 y más allá:

class Singleton  {
   static let sharedInstance = Singleton()
}

Con una prueba de corrección (todo el crédito va aquí), hay poco o ningún motivo para usar cualquiera de los métodos anteriores para singletons.

Actualizar: Este es ahora el oficial forma de definir singletons como se describe en el documentos oficiales!

En cuanto a las preocupaciones sobre el uso static vs class. static debería ser el que usar incluso cuando class las variables están disponibles Los singletons no están destinados a ser subclassed ya que eso daría lugar a múltiples instancias del singleton base. Utilizando static hace cumplir esto de una manera hermosa, Swifty.

Para Swift 1.0 y 1.1:

Con los recientes cambios en Swift, en su mayoría nuevos métodos de control de acceso, ahora me inclino por la forma más limpia de usar una variable global para singletons.

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

Como se menciona en el artículo del blog Swift aquí:

El inicializador lento para una variable global (también para miembros estáticos de   structs y enumeraciones) se ejecuta la primera vez que se accede a global, y   se inicia como dispatch_once para asegurarse de que la inicialización sea   atómico. Esto permite una manera genial de usar dispatch_once en tu código:   solo declare una variable global con un inicializador y márquelo   privado.

Esta forma de crear un singleton es segura para subprocesos, rápida, floja y también enlazada a ObjC de manera gratuita.


160
2018-02-10 16:03



Swift 1.2 o posterior ahora admite variables / constantes estáticas en las clases. Entonces puedes usar una constante estática:

class MySingleton {

    static let sharedMySingleton = MySingleton()

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

45
2018-06-05 18:02



Hay una mejor manera de hacerlo. Puede declarar una variable global en su clase por encima de la declinación de clase como tal

var tpScopeManagerSharedInstance = TPScopeManager()

Esto simplemente llama a su init predeterminado o cualquiera de las variables init y globales son dispatch_once por defecto en Swift. Luego, en cualquier clase que desee obtener una referencia, simplemente haga esto:

var refrence = tpScopeManagerSharedInstance
// or you can just access properties and call methods directly
tpScopeManagerSharedInstance.someMethod()

Entonces, básicamente, puedes deshacerte de todo el bloque del código de instancia compartido.


32
2018-01-13 03:36



Los singleton Swift están expuestos en los marcos de Cocoa como funciones de clase, p. NSFileManager.defaultManager(), NSNotificationCenter.defaultCenter(), por lo que creo que tiene más sentido como función de clase reflejar este comportamiento, en lugar de una variable de clase como otras soluciones usan, p.

class MyClass {

    private static let _sharedInstance = MyClass()

    class func sharedInstance() -> MyClass {
        return _sharedInstance
    }
}

Recuperar el singleton via MyClass.sharedInstance().


27
2017-09-09 12:51



Swift 4+

protocol Singleton: class {
    static var sharedInstance: Self { get }
}

final class Kraken: Singleton {
    static let sharedInstance = Kraken()
    private init() {}
}

15
2017-09-18 07:31



Por el Documentación de Apple, se ha repetido muchas veces que la forma más fácil de hacer esto en Swift es con una propiedad de tipo estático:

class Singleton {
    static let sharedInstance = Singleton()
}

Sin embargo, si está buscando una forma de realizar una configuración adicional más allá de una simple llamada de constructor, el secreto es usar un cierre invocado inmediatamente:

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}

Esto está garantizado para ser seguro para subprocesos y se inicializa de forma perezosa solo una vez.


14
2018-06-05 02:29



Al mirar el código de muestra de Apple encontré este patrón. No estoy seguro de cómo Swift se ocupa de la estática, pero esto sería seguro en C #. Incluyo tanto la propiedad como el método para la interpolación Objective-C.

struct StaticRank {
    static let shared = RankMapping()
}

class func sharedInstance() -> RankMapping {
    return StaticRank.shared
}

class var shared:RankMapping {
    return StaticRank.shared
}

9
2018-06-15 16:28



Si planea usar su clase Singleton de Swift en Objective-C, esta configuración hará que el compilador genere los encabezados Objective-C apropiados:

class func sharedStore() -> ImageStore {
struct Static {
    static let instance : ImageStore = ImageStore()
    }
    return Static.instance
}

Luego, en la clase Objective-C puede llamar a su singleton como lo hizo en días anteriores a Swift:

[ImageStore sharedStore];

Esta es solo mi simple implementación.


5
2017-08-24 17:33



Primera solución

let SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Más adelante en tu código:

func someFunction() {        
    var socketManager = SocketManager        
}

Segunda solución

func SocketManager() -> SocketManagerSingleton {
    return _SocketManager
}
let _SocketManager = SocketManagerSingleton();

class SocketManagerSingleton {

}

Y más adelante en su código podrá mantener llaves para una menor confusión:

func someFunction() {        
    var socketManager = SocketManager()        
}

5
2018-06-06 08:01