tutorial - ¿A dónde dispatch_once en Swift 3?
nsoperationqueue swift 3 example (7)
Ejemplo para "dispatch_once" en Swift 3.0
Paso 1: solo reemplace el código a continuación con su Singleton.swift (clase Singleton)
// Singleton Class
class Singleton: NSObject {
var strSample = NSString()
static let sharedInstance:Singleton = {
let instance = Singleton ()
return instance
} ()
// MARK: Init
override init() {
print("My Class Initialized")
// initialized with variable or property
strSample = "My String"
}
}
Paso 2: Llame a Singleton desde ViewController.swift
// ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let mySingleton = Singleton.sharedInstance
print(mySingleton.strSample)
mySingleton.strSample = "New String"
print(mySingleton.strSample)
let mySingleton1 = Singleton.sharedInstance
print(mySingleton1.strSample)
}
Imagen de muestra de ViewController
Salida como esta
My Class Initialized
My String
New String
New String
Bien, descubrí la nueva
API Swifty Dispatch
en Xcode 8. Me estoy divirtiendo con
DispatchQueue.main.async
, y he estado navegando por el módulo
Dispatch
en Xcode para encontrar todas las nuevas API.
Pero también utilizo
dispatch_once
para asegurarme de que cosas como la creación de singleton y la configuración de una sola vez no se ejecuten más de una vez (incluso en un entorno multiproceso) ... y
dispatch_once
no se encuentra en ninguna parte del nuevo módulo Dispatch.
static var token: dispatch_once_t = 0
func whatDoYouHear() {
print("All of this has happened before, and all of it will happen again.")
dispatch_once(&token) {
print("Except this part.")
}
}
De acuerdo con la Guía de Migración :
La función gratuita dispatch_once ya no está disponible en Swift. En Swift, puede usar propiedades globales estáticas o propiedades estáticas con inicialización perezosa y obtener la misma seguridad de subprocesos y garantías de llamada única que se proporciona dispatch_once.
Ejemplo:
let myGlobal = { … global contains initialization in a call to a closure … }()
// using myGlobal will invoke the initialization code only the first time it is used.
_ = myGlobal
Desde Swift 1.x, Swift ha estado utilizando
dispatch_once
detrás de escena
para realizar una inicialización diferida segura de subprocesos de variables globales y propiedades estáticas.
Entonces, la
static var
anterior ya estaba usando
dispatch_once
, lo que lo hace un poco extraño (y posiblemente problemático usarlo nuevamente como un token para otro
dispatch_once
. De hecho, no hay una forma segura de usar
dispatch_once
sin este tipo de recursión, por lo que obtuvieron deshazte de él. En su lugar, solo usa las características del lenguaje creadas en él:
// global constant: SomeClass initializer gets called lazily, only on first use
let foo = SomeClass()
// global var, same thing happens here
// even though the "initializer" is an immediately invoked closure
var bar: SomeClass = {
let b = SomeClass()
b.someProperty = "whatever"
b.doSomeStuff()
return b
}()
// ditto for static properties in classes/structures/enums
class MyClass {
static let singleton = MyClass()
init() {
print("foo")
}
}
Entonces, todo eso es excelente si ha estado utilizando
dispatch_once
para una
inicialización
única que da como resultado algún valor; simplemente puede hacer que ese valor sea la variable global o la propiedad estática que está inicializando.
Pero, ¿qué sucede si está utilizando
dispatch_once
para hacer un trabajo que no necesariamente tiene un resultado?
Todavía puede hacerlo con una variable global o propiedad estática: simplemente haga que el tipo de esa variable sea
Void
:
let justAOneTimeThing: () = {
print("Not coming back here.")
}()
Y si acceder a una variable global o una propiedad estática para realizar un trabajo de una sola vez no le parece correcto, por ejemplo, quiere que sus clientes llamen a una función "inicializarme" antes de que trabajen con su biblioteca, simplemente envuelva eso acceso en una función:
func doTheOneTimeThing() {
justAOneTimeThing
}
Vea la guía de migración para más.
Las otras respuestas aquí y alrededor de las interwebs son bastante buenas, pero creo que este pequeño dato también debe mencionarse:
Lo mejor de
dispatch_once
fue lo optimizado que estaba, esencialmente eliminando el código después de la primera ejecución de una manera que apenas entiendo, pero estoy razonablemente seguro de que sería mucho más rápido que configurar y verificar un token global (real).
Si bien el token podría implementarse razonablemente en Swift, tener que declarar otro booleano almacenado no es tan bueno. Sin mencionar que no es seguro. Como dice el documento , debe usar un "globalizado perezosamente inicializado". Sí, pero ¿por qué abarrotar el alcance global, verdad?
Hasta que alguien me convenza de un método mejor, tiendo a declarar mi cierre de una vez dentro del alcance en el que lo usaré, o lo cerraré razonablemente de la siguiente manera:
private lazy var foo: Void = {
// Do this once
}()
Básicamente digo que "cuando leo esto,
foo
debería ser el resultado de ejecutar este bloque".
Se comporta exactamente de la misma manera
let
haría una constante global
let
, solo en el ámbito correcto.
Y más bonita.
Luego lo llamaría donde quisiera, al leerlo en algo que nunca se usará de otra manera.
Me gusta el
_
de Swift por eso.
Al igual que:
_ = foo
Esta peculiaridad realmente genial ha existido por un tiempo, pero no ha visto mucho amor.
Básicamente, deja la variable sola en el tiempo de ejecución, como un cierre no llamado, hasta que algo quiere ver su resultado
Void
.
En la lectura, llama al cierre, lo tira y mantiene su resultado en
foo
.
Void
utiliza prácticamente nada en cuanto a memoria, por lo que las lecturas posteriores (es decir,
_ = foo
) no hacen nada en la CPU.
(¡No me cite sobre eso, alguien por favor revise el ensamblaje para estar seguro!) ¡Tenga tantos como quiera, y Swift básicamente deja de preocuparse por eso después de la primera ejecución!
¡Pierda ese viejo
dispatch_once_t
y conserve mucho de su código tan bonito como cuando lo abrió por primera vez el día de Navidad!
Mi único problema es que puedes configurar
foo
para otra cosa antes de su primera lectura, ¡y luego tu código
nunca
será llamado!
De ahí la constante global
let
, que impide eso.
La cosa es que las constantes en el ámbito de la clase no juegan bien con
self
, por lo que no se juega con variables de instancia ... Pero en serio, ¿cuándo configuras
algo
en
Void
todos modos?
Eso, y necesitaría especificar el tipo de retorno como
Void
o
()
, de lo contrario, todavía se quejará de
self
.
Who''da thunk?
Y
lazy
es solo para hacer que la variable sea tan floja como debería ser, para que Swift no la ejecute directamente en
init()
.
Bastante elegante, ¡siempre y cuando recuerdes no escribirle! :PAGS
Si bien el patrón "lazy var" me permite dejar de preocuparme por los tokens de despacho y, en general, es más conveniente que
dispatch_once()
, no me gusta cómo se ve en el sitio de llamadas:
_ = doSomethingOnce
Esperaría que esta declaración se parezca más a una llamada de función (ya que implica acción), pero no lo parece en absoluto.
Además, tener que escribir
_ =
para descartar explícitamente el resultado es innecesario y molesto.
Hay una mejor manera:
lazy var doSomethingOnce: () -> Void = {
print("executed once")
return {}
}()
Lo que hace posible lo siguiente:
doSomethingOnce()
Esto podría ser menos eficiente (ya que llama a un cierre vacío en lugar de simplemente descartar un
Void
), pero la claridad mejorada es totalmente valiosa para mí.
Thread-safe dispatch_once:
private lazy var foo: Void = {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
// Do this once
}()
Compila bajo Xcode 8 GA Swift 3
La forma recomendada y elegante de crear una instancia de clase individualton dispatch_once:
final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...
Para usarlo:
if TheRoot.shared.appState == .normal {
...
}
¿Qué hacen estas líneas?
final - por lo que la clase no puede ser anulada, extendida, también hace que el código sea algo más rápido de ejecutar, menos indirectas.
static let shared = TheRoot (): esta línea realiza un inicio diferido y solo se ejecuta una vez.
Esta solución es segura para subprocesos.