Usando un modelo singleton Dispatch_once en Swift

Resuelto David Berry asked hace 10 años • 30 respuestas

Estoy intentando encontrar un modelo singleton apropiado para su uso en Swift. Hasta ahora, he podido conseguir que un modelo sin subprocesos funcione 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 singleton en la estructura estática debería permitir una instancia única que no colisione con instancias singleton sin esquemas de nombres complejos, y debería hacer que las cosas sean bastante privadas. Sin embargo, obviamente este modelo no es seguro para subprocesos. Así que intenté agregar dispatch_oncea todo:

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 aparece un error del compilador en la dispatch_oncelínea:

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

Probé 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 correcto de dispatch_onceSwift? Inicialmente pensé que el problema estaba en el bloque debido al ()mensaje de error, pero cuanto más lo miro, más creo que puede ser una cuestión de definirlo dispatch_once_tcorrectamente.

David Berry avatar Jun 04 '14 03:06 David Berry
Aceptado

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

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

Constante de clase

class Singleton  {
   static let sharedInstance = Singleton()
}

Este enfoque admite la inicialización diferida porque Swift inicializa de forma diferida constantes (y variables) de clase y es seguro para subprocesos según la definición de let. Esta es ahora la forma oficialmente recomendada de crear una instancia de un singleton.

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

estructura anidada

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

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

despacho_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 pongo aquí de todos modos porque encuentro interesantes las diferencias en la sintaxis.

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

Consulte este proyecto de GitHub para pruebas unitarias.

hpique avatar Jun 10 '2014 17:06 hpique

Dado que Apple ahora ha aclarado que las variables de estructura estática se inicializan tanto de forma diferida como envueltas dispatch_once(consulte 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 diferida y segura para subprocesos de elementos de estructura estática, oculta de forma segura la implementación real del consumidor, mantiene todo compartimentado de forma compacta para mayor legibilidad y elimina una variable global visible.

Apple ha aclarado que los inicializadores diferidos son seguros para subprocesos, por lo que no hay necesidad de dispatch_onceprotecciones similares.

El inicializador diferido para 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 despacho_once para garantizar que la inicialización sea atómica. Esto permite una forma genial de usar Dispatch_once en su código: simplemente declare una variable global con un inicializador y márquela como privada.

De aquí

David Berry avatar Jun 06 '2014 01:06 David Berry

Para Swift 1.2 y posteriores:

class Singleton  {
   static let sharedInstance = Singleton()
}

Con una prueba de corrección (todo el crédito va aquí ), ahora hay poca o ninguna razón para utilizar cualquiera de los métodos anteriores para singletons.

Actualización : ¡Esta es ahora la forma oficial de definir singletons como se describe en los documentos oficiales !

En cuanto a las preocupaciones sobre el uso staticvs. debe ser el que se utilice incluso cuando las variables estén disponibles. Los singleton no están destinados a subclasificarse, ya que eso daría como resultado múltiples instancias del singleton base. El uso refuerza esto de una manera hermosa y rápida.classstaticclassstatic

Para Swift 1.0 y 1.1:

Con los cambios recientes en Swift, en su mayoría nuevos métodos de control de acceso, ahora me inclino por una 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 de Swift aquí :

El inicializador diferido para 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 despacho_once para garantizar que la inicialización sea atómica. Esto permite una forma genial de usar Dispatch_once en su código: simplemente declare una variable global con un inicializador y márquela como privada.

Esta forma de crear un singleton es segura para subprocesos, rápida, diferida y también está conectada a ObjC de forma gratuita.

Jack avatar Jun 03 '2014 20:06 Jack

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

class MySingleton {

    static let sharedMySingleton = MySingleton()

    private init() {
        // ...
    }
}
Florian avatar Feb 10 '2015 16:02 Florian