2016-06-17 12 views
56

Qual è la nuova sintassi per dispatch_once in Swift dopo le modifiche apportate nella lingua versione 3? La vecchia versione era la seguente.dispatch_once dopo la modifica dell'API Swift 3 GCD

var token: dispatch_once_t = 0 
func test() { 
    dispatch_once(&token) { 
    } 
} 

Questi are the changes to libdispatch sono stati creati.

+0

Eventuali duplicati: [Dove DISP atch_once in Swift 3?] (http://stackoverflow.com/q/37801407/957768) – rickster

+0

In base alle risposte https://stackoverflow.com/a/38311178/1648724 e https://stackoverflow.com/a/ 39983813/1648724, ho creato un CocoaPod per farlo: ['pod 'SwiftDispatchOnce', '~> 1.0''] (https://github.com/JRG-Developer/SwiftDispatchOnce) Saluti. :] –

risposta

38

Dal doc:

spedizione
La funzione dispatch_once gratuito non è più disponibile in Swift. In Swift, è possibile utilizzare globali inizializzati pigramente o proprietà statiche e ottenere la stessa sicurezza di thread e garanzie chiamate una volta come dispatch_once fornito. 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. 
+2

Non è come se non sapessi che Swift cambierebbe rapidamente e dovresti correggere un sacco di codice rotto tra le versioni di Swift. – Abizern

+2

il dolore maggiore sono i pod di terze parti che non sono sempre compatibili con Swift3. – Tinkerbell

+4

Questo è il debito tecnico che si accumula quando si introducono dipendenze di terze parti, @Tinkerbell. Amo Swift ma sono più cauto nel portare dipendenze esterne che lo usano proprio per questo motivo. –

72

Mentre usando globali inizializzati pigri può avere senso per qualche inizializzazione una volta, non ha senso per altri tipi. Ha molto senso usare globals pigri inizializzati per cose come i singleton, non ha molto senso per cose come la protezione di un setup swizzle.

Ecco un'implementazione Swift 3 stile 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!") 
} 

Come siamo attualmente in un tempo di transizione da rapido 2 a 3, ecco un esempio di implementazione di swift 2:

+0

Grazie mille per la soluzione. Mi stavo esattamente intrappolando in una configurazione swizzle. Spero che il gruppo rapido affronti questo caso d'uso. – salman140

+0

Non dovresti assolutamente usare più 'objc_sync_enter' e' objc_sync_exit'. – smat88dd

+0

E perché? –

35

Espandendo la risposta di Tod Cunningham sopra, ho aggiunto un altro metodo che rende il token automaticamente da file, funzione e linea.

public extension DispatchQueue { 
    private static var _onceTracker = [String]() 

    public class func once(file: String = #file, function: String = #function, line: Int = #line, block:(Void)->Void) { 
     let token = file + ":" + function + ":" + String(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)->Void) { 
     objc_sync_enter(self) 
     defer { objc_sync_exit(self) } 


     if _onceTracker.contains(token) { 
      return 
     } 

     _onceTracker.append(token) 
     block() 
    } 
} 

Così può essere più semplice di chiamare:

DispatchQueue.once { 
    setupUI() 
} 

e si può ancora indicare un gettone se lo si desidera:

DispatchQueue.once(token: "com.me.project") { 
    setupUI() 
} 

Suppongo che si possa ottenere una collisione se si dispone di lo stesso file in due moduli. Peccato che non ci sia #module

+0

davvero utile grazie – Svitlana

+0

questo getta un po 'più di luce su cosa sta succedendo. Grazie. – nyxee

5

È comunque possibile utilizzare se si aggiunge un'intestazione colmare:

typedef dispatch_once_t mxcl_dispatch_once_t; 
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block); 

Poi, in un .m da qualche parte:

void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) { 
    dispatch_once(predicate, block); 
} 

Ora dovrebbe essere in grado di utilizzare mxcl_dispatch_once da Swift.

Principalmente si dovrebbe usare quello che Apple suggerisce, ma ho avuto alcuni usi legittimi in cui avevo bisogno di dispatch_once con un singolo token in due funzioni e non è coperto da ciò che Apple fornisce invece.

-1

Utilizzare l'approccio costante di classe se si utilizza Swift 1.2 o successivo e l'approccio di struttura nid se è necessario supportare versioni precedenti. Un'esplorazione del pattern Singleton in Swift. Tutti gli approcci seguenti supportano l'inizializzazione pigra e la sicurezza del thread. approccio dispatch_once non è lavorato in rapida 3.0

Approccio A: Classe costante class SingletonA {

static let sharedInstance = SingletonA() 

init() { 
    println("AAA"); 
} 

} Approccio B: struct nidificato 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! 
} 

}

+0

La domanda posta in particolare su una soluzione per Swift 3. – thesummersign

5

Swift 3: Per chi ama classi riutilizzabili (o strutture):

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

Usage:

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 deprecazione dovuti 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) 
    } 
} 
+0

Viene visualizzato un messaggio che OSSSpinLock è deprecato in iOS 10.0 – markhorrocks

+2

Grazie! Codice di esempio aggiornato 'OSSpinLock' sostituito con' os_unfair_lock'. BTW: Ecco un buon video WWDC su 'Concurrent Programming': https://developer.apple.com/videos/play/wwdc2016/720/ – Vlad

8

soluzione più semplice è

lazy var dispatchOnce : Void = { // or anyName I choose 

    self.title = "Hello Lazy Guy" 

    return 
}() 

utilizzato come

override func viewDidLayoutSubviews() { 
    super.viewDidLayoutSubviews() 
    _ = dispatchOnce 
} 
+0

Questo non aiuta affatto, perché una dichiarazione lazy var non può essere fatta in linea con codice normale, deve essere in una struct o in una definizione di classe. Ciò significa che il contenuto di dispatchOnce non può acquisire l'ambito circostante di un'istanza. Ad esempio se dichiari una chiusura che non è ancora stata eseguita, non puoi quindi dichiarare la struct all'interno di quella chiusura e avere il contenuto della var pig essere un'altra chiusura che cattura vars dalla chiusura circostante ... – CommaToast

+1

Downvoted perché questo codice ha sicuramente ** non ** la stessa semantica di dispatch_once. dispatch_once garantisce che il codice venga eseguito esattamente una volta, ** qualunque thread lo chiami da **. Gli oggetti pigri hanno un comportamento indefinito in un ambiente multi-thread. – Frizlab

-3
I have created below function 

func executeOnce(code: @escaping() -> Void) 
     { 
       if UserDefaults.standard.value(forKey: "3333##112233") == nil 
       { 
        code() 
        UserDefaults.standard.setValue("vv", forKey: "3333##112233") 
        UserDefaults.standard.synchronize() 
       } 
     } 

e utilizzare come di seguito

executeOnce { 

     print("onces") 
    }