¿Para qué sirve la nueva sintaxis dispatch_once
en Swift después de los cambios realizados en la versión de idioma 3? La versión anterior era la siguiente.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
¿Para qué sirve la nueva sintaxis dispatch_once
en Swift después de los cambios realizados en la versión de idioma 3? La versión anterior era la siguiente.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
pod 'SwiftDispatchOnce', '~> 1.0'
Cheers. :]
Respuestas:
Del doc :
Despacho
La función gratuita dispatch_once ya no está disponible en Swift. En Swift, puede usar propiedades globales o estáticas inicializadas de manera perezosa y obtener las mismas garantías de seguridad de subprocesos y llamadas una vez que dispatch_once. Ejemplo:
let myGlobal: () = { … global contains initialization in a call to a closure … }()
_ = myGlobal // using myGlobal will invoke the initialization code only the first time it is used.
dispatch_once
estaba claro. Esto, desafortunadamente, es feo y confuso ..
Si bien el uso de globales inicializados diferidos puede tener sentido para una inicialización única, no tiene sentido para otros tipos. Tiene mucho sentido usar globales perezosos inicializados para cosas como singletons, no tiene mucho sentido para cosas como proteger una configuración de swizzle.
Aquí hay una implementación estilo Swift 3 de dispatch_once:
public extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block:@noescape(Void)->Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
Aquí hay un ejemplo de uso:
DispatchQueue.once(token: "com.vectorform.test") {
print( "Do This Once!" )
}
o usando un UUID
private let _onceToken = NSUUID().uuidString
DispatchQueue.once(token: _onceToken) {
print( "Do This Once!" )
}
Dado que actualmente estamos en un momento de transición de Swift 2 a 3, aquí hay un ejemplo de implementación de Swift 2:
public class Dispatch
{
private static var _onceTokenTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token token: String, @noescape block:dispatch_block_t) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTokenTracker.contains(token) {
return
}
_onceTokenTracker.append(token)
block()
}
}
objc_sync_enter
y objc_sync_exit
más.
Ampliando la respuesta de Tod Cunningham anterior, agregué otro método que crea el token automáticamente desde el archivo, la función y la línea.
public extension DispatchQueue {
private static var _onceTracker = [String]()
public class func once(file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String,
block: () -> Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
Entonces puede ser más simple llamar:
DispatchQueue.once {
setupUI()
}
y aún puede especificar un token si lo desea:
DispatchQueue.once(token: "com.hostname.project") {
setupUI()
}
Supongo que podría producirse una colisión si tiene el mismo archivo en dos módulos. Lástima que no haya#module
Editar
La solución simple es
lazy var dispatchOnce : Void = { // or anyName I choose
self.title = "Hello Lazy Guy"
return
}()
usado como
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = dispatchOnce
}
Aún puede usarlo si agrega un encabezado de puente:
typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);
Luego en .m
algún lugar:
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
dispatch_once(predicate, block);
}
Ahora debería poder utilizar mxcl_dispatch_once
Swift.
En su mayoría, debería usar lo que Apple sugiere, pero tuve algunos usos legítimos en los que necesitaba dispatch_once
con un solo token en dos funciones y no está cubierto por lo que Apple proporciona.
Puede declarar una función variable de nivel superior como esta:
private var doOnce: ()->() = {
/* do some work only once per instance */
return {}
}()
luego llame a esto en cualquier lugar:
doOnce()
Swift 3: para aquellos a los que les gustan las clases (o estructuras) reutilizables:
public final class /* struct */ DispatchOnce {
private var lock: OSSpinLock = OS_SPINLOCK_INIT
private var isInitialized = false
public /* mutating */ func perform(block: (Void) -> Void) {
OSSpinLockLock(&lock)
if !isInitialized {
block()
isInitialized = true
}
OSSpinLockUnlock(&lock)
}
}
Uso:
class MyViewController: UIViewController {
private let /* var */ setUpOnce = DispatchOnce()
override func viewWillAppear() {
super.viewWillAppear()
setUpOnce.perform {
// Do some work here
// ...
}
}
}
Actualización (28 de abril de 2017): OSSpinLock
reemplazada con os_unfair_lock
advertencias de obsolescencia debida en macOS SDK 10.12.
public final class /* struct */ DispatchOnce {
private var lock = os_unfair_lock()
private var isInitialized = false
public /* mutating */ func perform(block: (Void) -> Void) {
os_unfair_lock_lock(&lock)
if !isInitialized {
block()
isInitialized = true
}
os_unfair_lock_unlock(&lock)
}
}
OSSpinLock
reemplazado con os_unfair_lock
. Por cierto: Aquí hay un buen video de la WWDC sobre Concurrent Programming
: developer.apple.com/videos/play/wwdc2016/720
Mejoro las respuestas anteriores obtengo resultado:
import Foundation
extension DispatchQueue {
private static var _onceTracker = [AnyHashable]()
///only excute once in same file&&func&&line
public class func onceInLocation(file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
///only excute once in same Variable
public class func onceInVariable(variable:NSObject, block: () -> Void){
once(token: variable.rawPointer, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: AnyHashable,block: () -> Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}
}
extension NSObject {
public var rawPointer:UnsafeMutableRawPointer? {
get {
Unmanaged.passUnretained(self).toOpaque()
}
}
}
Use 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. Una exploración del patrón Singleton en Swift. Todos los enfoques siguientes admiten la inicialización diferida y la seguridad de subprocesos. El enfoque dispatch_once no funciona en Swift 3.0
Método A: constante de clase
class SingletonA {
static let sharedInstance = SingletonA()
init() {
println("AAA");
}
}
Método B: estructura anidada
class SingletonB {
class var sharedInstance: SingletonB {
struct Static {
static let instance: SingletonB = SingletonB()
}
return Static.instance
}
}
Enfoque C: dispatch_once
class SingletonC {
class var sharedInstance: SingletonC {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: SingletonC? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SingletonC()
}
return Static.instance!
}
}