Qual è la nuova sintassi dispatch_once
in Swift dopo le modifiche apportate nella versione 3 della lingua? La vecchia versione era la seguente.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
Qual è la nuova sintassi dispatch_once
in Swift dopo le modifiche apportate nella versione 3 della lingua? La vecchia versione era la seguente.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
pod 'SwiftDispatchOnce', '~> 1.0'
Acclamazioni. :]
Risposte:
Dal doc :
Spedizione
La funzione gratuita dispatch_once non è più disponibile in Swift. In Swift, puoi utilizzare valori globali o proprietà statiche inizializzati pigramente e ottenere le stesse garanzie di thread safety e call-once fornite da dispatch_once. Esempio:
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
era chiaro. Questo, purtroppo, è brutto e confuso ..
Sebbene l'utilizzo di variabili globali inizializzate pigre possa avere senso per alcune inizializzazioni una tantum, non ha senso per altri tipi. Ha molto senso usare variabili globali inizializzate pigre per cose come i singleton, non ha molto senso per cose come la protezione di una configurazione swizzle.
Ecco un'implementazione in stile Swift 3 di 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()
}
}
Ecco un esempio di utilizzo:
DispatchQueue.once(token: "com.vectorform.test") {
print( "Do This Once!" )
}
o utilizzando un UUID
private let _onceToken = NSUUID().uuidString
DispatchQueue.once(token: _onceToken) {
print( "Do This Once!" )
}
Poiché attualmente siamo in un periodo di transizione da swift 2 a 3, ecco un esempio di implementazione di 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
e objc_sync_exit
più.
Espandendo la risposta di Tod Cunningham sopra, ho aggiunto un altro metodo che rende automaticamente il token da file, funzione e riga.
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()
}
}
Quindi può essere più semplice chiamare:
DispatchQueue.once {
setupUI()
}
e puoi ancora specificare un token se lo desideri:
DispatchQueue.once(token: "com.hostname.project") {
setupUI()
}
Suppongo che potresti ottenere una collisione se hai lo stesso file in due moduli. Peccato che non ci sia#module
modificare
La soluzione semplice è
lazy var dispatchOnce : Void = { // or anyName I choose
self.title = "Hello Lazy Guy"
return
}()
usato come
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = dispatchOnce
}
Puoi ancora usarlo se aggiungi un'intestazione di bridging:
typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);
Poi da .m
qualche parte:
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
dispatch_once(predicate, block);
}
Ora dovresti essere in grado di utilizzare mxcl_dispatch_once
da Swift.
Principalmente dovresti usare ciò che Apple suggerisce invece, ma ho avuto alcuni usi legittimi in cui avevo bisogno di dispatch_once
un singolo token in due funzioni e non è coperto da ciò che Apple fornisce invece.
Puoi dichiarare una funzione variabile di primo livello come questa:
private var doOnce: ()->() = {
/* do some work only once per instance */
return {}
}()
quindi chiamalo ovunque:
doOnce()
Swift 3: per coloro a cui piacciono le classi (o strutture) riutilizzabili:
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)
}
}
Utilizzo:
class MyViewController: UIViewController {
private let /* var */ setUpOnce = DispatchOnce()
override func viewWillAppear() {
super.viewWillAppear()
setUpOnce.perform {
// Do some work here
// ...
}
}
}
Aggiornamento (28 aprile 2017): OSSpinLock
sostituito con os_unfair_lock
avvisi di ritiro scaduto in 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
sostituito con os_unfair_lock
. BTW: Ecco un buon video WWDC su Concurrent Programming
: developer.apple.com/videos/play/wwdc2016/720
Miglioramento sopra le risposte ottengo risultato:
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()
}
}
}
Utilizza l'approccio della costante di classe se stai utilizzando Swift 1.2 o versioni successive e l'approccio della struttura nidificata se devi supportare le versioni precedenti. Un'esplorazione del pattern Singleton in Swift. Tutti gli approcci seguenti supportano l'inizializzazione lenta e la sicurezza dei thread. L'approccio dispatch_once non funziona in Swift 3.0
Approccio A: costante di classe
class SingletonA {
static let sharedInstance = SingletonA()
init() {
println("AAA");
}
}
Approccio B: struttura annidata
class SingletonB {
class var sharedInstance: SingletonB {
struct Static {
static let instance: SingletonB = SingletonB()
}
return Static.instance
}
}
Approccio 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!
}
}