dispatch_after - GCD in Swift?


558

Ho esaminato l' iBook di Apple e non sono riuscito a trovarne alcuna definizione:

Qualcuno può spiegare la struttura di dispatch_after?

dispatch_after(<#when: dispatch_time_t#>, <#queue: dispatch_queue_t?#>, <#block: dispatch_block_t?#>)

1
Apple non ha pubblicato questo libro nel 2018. L' ultimo archivio che ho trovato è del dicembre 2017 . I vecchi collegamenti a iBook ora reindirizzano semplicemente a developer.apple.com/documentation/swift .
Cœur

Risposte:


742

Un'idea più chiara della struttura:

dispatch_after(when: dispatch_time_t, queue: dispatch_queue_t, block: dispatch_block_t?)

dispatch_time_tè un UInt64. In dispatch_queue_trealtà il tipo è aliasato per un NSObject, ma dovresti semplicemente usare i tuoi metodi GCD familiari per ottenere le code. Il blocco è una chiusura Swift. In particolare, dispatch_block_tè definito come () -> Void, che equivale a () -> ().

Esempio di utilizzo:

let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
    print("test")
}

MODIFICARE:

Consiglio di usare la delayfunzione davvero bella di @ matt .

EDIT 2:

In Swift 3, ci saranno nuovi wrapper per GCD. Vedi qui: https://github.com/apple/swift-evolution/blob/master/proposals/0088-libdispatch-for-swift3.md

L'esempio originale verrebbe scritto come segue in Swift 3:

let deadlineTime = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
    print("test")
}

Si noti che è possibile scrivere la deadlineTimedichiarazione come DispatchTime.now() + 1.0e ottenere lo stesso risultato perché l' +operatore viene sovrascritto come segue (in modo simile per -):

  • func +(time: DispatchTime, seconds: Double) -> DispatchTime
  • func +(time: DispatchWalltime, interval: DispatchTimeInterval) -> DispatchWalltime

Ciò significa che se non si utilizza DispatchTimeInterval enume si scrive semplicemente un numero, si presume che si stiano utilizzando i secondi.


17
Suggerimento: poiché il blocco è l'ultimo parametro della funzione, è possibile utilizzare la sintassi "chiusura finale" di Swift per una maggiore leggibilità:dispatch_after(1, dispatch_get_main_queue()) { println("test") }
Bill

8
Penso che l'uso del numero 1in dispatch_after(1, ...possa creare molta confusione qui. La gente penserà che è un numero di secondi, quando in realtà è nano-secondo . Suggerisco di vedere la risposta di @brindy su come creare correttamente questo numero.
Hlung,

3
Passare 1a dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))perché causa confusione. La gente potrebbe pensare che non è necessario creare un dispatch_time_t in Swift
OemerA

4
La versione di Swift 3 non sembra funzionare. Si lamenta che Binary operator '+' cannot be applied to operands of type DispatchTime and '_'sulla linealet delayTime = DispatchTime.now() + .seconds(1.0)
Andy Ibanez,

9
Riscriverlo come DispatchTime.now() + 1.0sembra essere l'unico modo per farlo funzionare (non c'è bisogno di .seconds)
Andy Ibanez,

1093

Uso dispatch_aftercosì spesso che ho scritto una funzione di utilità di livello superiore per semplificare la sintassi:

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

E ora puoi parlare così:

delay(0.4) {
    // do stuff
}

Wow, una lingua in cui puoi migliorare la lingua. Cosa potrebbe esserci di meglio?


Aggiornamento per Swift 3, Xcode 8 Seed 6

Sembra quasi non preoccuparsene, ora che hanno migliorato la sintassi della chiamata:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

2
Avevo solo bisogno di scorciatoie per il calcolo del ritardo, finito con:func delayInSec(delay: Double) -> dispatch_time_t { return dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))) }
Aviel Gross

4
@ agf119105 Se nella chiusura è presente una sola riga di codice, aggiungere un'altra riga di codice (ad es return.).
matt

2
@GastonM Irrilevante. Il passaggio di una funzione non ha di per sé problemi di gestione della memoria.
matt

7
"Una lingua dove puoi migliorare la lingua". Non capisco come la definizione di una funzione globale stia migliorando la lingua, o perché ciò non sia fattibile nemmeno in C. Forse se sovraccarichi un operatore;)1.0 ~~ { code...}
Yerk il

8
Non mettere in discussione la correttezza della tua risposta, ma "Uso dispatch_after così spesso" non è un odore di codice che sarebbe meglio combattere se non fornissi una funzione di convenienza?
Nikolai Ruhe,

128

Swift 3+

Questo è semplicissimo ed elegante in Swift 3+:

DispatchQueue.main.asyncAfter(deadline: .now() + 4.5) {
    // ...
}

Risposta precedente:

Per espandere la risposta di Cezary, che verrà eseguita dopo 1 nanosecondo, ho dovuto eseguire le seguenti operazioni per eseguire dopo 4 secondi e mezzo.

let delay = 4.5 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), block)

Modifica: ho scoperto che il mio codice originale era leggermente sbagliato. La digitazione implicita provoca un errore di compilazione se non si esegue il cast di NSEC_PER_SEC su un doppio.

Se qualcuno può suggerire una soluzione più ottimale sarei desideroso di ascoltarlo.


Ottengo un errore del compilatore per un'API obsoleta con dispatch_get_current_queue(). Ho usato dispatch_get_main_queue()invece.
David L

@DavidL - grazie, dispatch_get_main_queue()è sicuramente quello che dovresti usare. Si aggiorna.
Brindy,

l'ho provato in un parco giochi con Swift 3 e non funziona
μολὼν.λαβέ

@GAlexander Funziona per me. Stai permettendo al parco giochi di essere eseguito a tempo indeterminato?
Brindy,

uhm, beh no, ho lasciato correre per un paio d'ore e ancora niente di stampato. ecco cosa ho usato. "import Dispatch import Darwin import CoreGraphics 'DispatchQueue.main.asyncAfter (deadline: .now () + 4.5) {print (" got here ")}"
μολὼν.λαβέ

83

La sintassi di Matt è molto bella e se devi invalidare il blocco, potresti voler usare questo:

typealias dispatch_cancelable_closure = (cancel : Bool) -> Void

func delay(time:NSTimeInterval, closure:()->Void) ->  dispatch_cancelable_closure? {

    func dispatch_later(clsr:()->Void) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(time * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), clsr)
    }

    var closure:dispatch_block_t? = closure
    var cancelableClosure:dispatch_cancelable_closure?

    let delayedClosure:dispatch_cancelable_closure = { cancel in
        if closure != nil {
            if (cancel == false) {
                dispatch_async(dispatch_get_main_queue(), closure!);
            }
        }
        closure = nil
        cancelableClosure = nil
    }

    cancelableClosure = delayedClosure

    dispatch_later {
        if let delayedClosure = cancelableClosure {
            delayedClosure(cancel: false)
        }
    }

    return cancelableClosure;
}

func cancel_delay(closure:dispatch_cancelable_closure?) {

    if closure != nil {
        closure!(cancel: true)
    }
}

Utilizzare come segue

let retVal = delay(2.0) {
    println("Later")
}
delay(1.0) {
    cancel_delay(retVal)
}

crediti

Il link sopra sembra essere inattivo. Codice Objc originale da Github


1
L'unica performance che ha performSelector: afterDelay è la possibilità di annullarla. Solo questa soluzione copre il problema. Grazie
HotJard il

@HotJard Nota che performSelector:afterDelay:ora è disponibile in Swift 2, quindi puoi annullarlo.
matt

@matt ma è disponibile solo per NSObject, non è vero?
HotJard

@HotJard Certo, ma è meglio che non averlo affatto. Non vedo alcun problema lì. Tuttavia, proprio come con questa risposta, avevo già compensato la sua perdita scrivendo un timer annullabile basato su GCD (usando un dispatch_source_t, perché è qualcosa che puoi cancellare).
opaco

2
Grazie mille, ho usato questo fino a Swift 2.3. Il compilatore Swift 3.0 si lamenta adesso, sarebbe fantastico se aggiorni la tua risposta!
automatico

27

La soluzione più semplice in Swift 3.0 e Swift 4.0 e Swift 5.0

func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { 
        completion()
    }
}

uso

delayWithSeconds(1) {
   //Do something
}

22

Apple ha uno snippet dispatch_after per Objective-C :

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    <#code to be executed after a specified delay#>
});

Ecco lo stesso frammento portato su Swift 3:

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + <#delayInSeconds#>) {
  <#code to be executed after a specified delay#>
}

14

Un altro modo è estendere Double in questo modo:

extension Double {
   var dispatchTime: dispatch_time_t {
       get {
           return dispatch_time(DISPATCH_TIME_NOW,Int64(self * Double(NSEC_PER_SEC)))
       }
   }
}

Quindi puoi usarlo in questo modo:

dispatch_after(Double(2.0).dispatchTime, dispatch_get_main_queue(), { () -> Void in
            self.dismissViewControllerAnimated(true, completion: nil)
    })

Mi piace la funzione di ritardo di Matt, ma per preferenza preferirei limitare il passaggio delle chiusure.


8

In Swift 3.0

Code di invio

  DispatchQueue(label: "test").async {
        //long running Background Task
        for obj in 0...1000 {
            print("async \(obj)")
        }

        // UI update in main queue
        DispatchQueue.main.async(execute: { 
            print("UI update on main queue")
        })

    }

    DispatchQueue(label: "m").sync {
        //long running Background Task
        for obj in 0...1000 {
            print("sync \(obj)")
        }

        // UI update in main queue
        DispatchQueue.main.sync(execute: {
            print("UI update on main queue")
        })
    }

Spedizione dopo 5 secondi

    DispatchQueue.main.after(when: DispatchTime.now() + 5) {
        print("Dispatch after 5 sec")
    }

4

Versione Swift 3.0

Dopo la funzione di chiusura, eseguire alcune attività dopo un ritardo sul thread principale.

func performAfterDelay(delay : Double, onCompletion: @escaping() -> Void){

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: {
       onCompletion()
    })
}

Chiamare questa funzione come:

performAfterDelay(delay: 4.0) {
  print("test")
}

4

1) Aggiungi questo metodo come parte dell'estensione UIViewController.

extension UIViewController{
func runAfterDelay(delay: NSTimeInterval, block: dispatch_block_t) {
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
        dispatch_after(time, dispatch_get_main_queue(), block)
    }
}

Chiama questo metodo su VC:

    self.runAfterDelay(5.0, block: {
     //Add code to this block
        print("run After Delay Success")
    })

2)

performSelector("yourMethod Name", withObject: nil, afterDelay: 1)

3)

override func viewWillAppear(animated: Bool) {

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue(), { () -> () in
    //Code Here
})

// Forma compatta

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue()) {
    //Code here
 }
}

3

Sebbene non sia la domanda originale del PO, alcune NSTimerdomande correlate sono state contrassegnate come duplicati di questa domanda, quindi vale la pena includere unNSTimer qui risposta.

NSTimer vs dispatch_after

  • NSTimerè più alto livello mentre dispatch_afterè più basso livello.
  • NSTimerè più facile da cancellare. L'annullamento dispatch_afterrichiede la scrittura di più codice .

Ritardare un'attività con NSTimer

Crea NSTimerun'istanza.

var timer = NSTimer()

Avvia il timer con il ritardo di cui hai bisogno.

// invalidate the timer if there is any chance that it could have been called before
timer.invalidate()
// delay of 2 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 

Aggiungi una funzione da chiamare dopo il ritardo (usando qualunque nome tu abbia usato per il selectorparametro sopra).

func delayedAction() {
    print("Delayed action has now started."
}

Appunti

  • Se devi annullare l'azione prima che accada, chiama semplicemente timer.invalidate().
  • Per un'azione ripetuta usare repeats: true.
  • Se si dispone di un evento singolo senza annullamento, non è necessario creare la timervariabile di istanza. Sarà sufficiente:

    NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 
  • Vedi la mia risposta più completa qui .


3

Per più funzioni usare questo. Questo è molto utile per usare animazioni o caricatore di attività per funzioni statiche o qualsiasi aggiornamento dell'interfaccia utente.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
            // Call your function 1
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                // Call your function 2
            }
        }

Ad esempio: utilizzare un'animazione prima che un tableView venga ricaricato. O qualsiasi altro aggiornamento dell'interfaccia utente dopo l'animazione.

*// Start your amination* 
self.startAnimation()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
                *// The animation will execute depending on the delay time*
                self.stopAnimation()
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    *// Now update your view*
                     self.fetchData()
                     self.updateUI()
                }
            }

2

Questo ha funzionato per me.

Swift 3:

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Sum of times: \(time1 + time2)")
}

Objective-C:

CGFloat time1 = 3.49;
CGFloat time2 = 8.13;

// Delay 2 seconds

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    CGFloat newTime = time1 + time2;
    NSLog(@"New time: %f", newTime);
});

2

Swift 3 e 4:

È possibile creare un'estensione su DispatchQueue e aggiungere un ritardo di funzione che utilizza internamente la funzione DispatchQueue asyncAfter

extension DispatchQueue {
    static func delay(_ delay: DispatchTimeInterval, closure: @escaping () -> ()) {
        let timeInterval = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: timeInterval, execute: closure)
    }
}

uso:

DispatchQueue.delay(.seconds(1)) {
    print("This is after delay")
}

1

Un altro aiuto per ritardare il tuo codice che è 100% Swift in uso e facoltativamente consente di scegliere un thread diverso per eseguire il tuo codice ritardato da:

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}

public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main:                 return DispatchQueue.main
        case .userInteractive:      return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated:        return DispatchQueue.global(qos: .userInitiated)
        case .utility:              return DispatchQueue.global(qos: .utility)
        case .background:           return DispatchQueue.global(qos: .background)
        }
    }
}

Ora puoi semplicemente ritardare il tuo codice sul thread principale in questo modo:

delay(bySeconds: 1.5) { 
    // delayed code
}

Se vuoi ritardare il tuo codice su un thread diverso :

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background thread
}

Se preferisci un Framework che ha anche alcune funzioni più utili, dai un'occhiata a HandySwift . Puoi aggiungerlo al tuo progetto tramite Cartagine, quindi usarlo esattamente come negli esempi sopra, ad esempio:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}

1

Preferisco sempre usare l'estensione invece delle funzioni gratuite.

Swift 4

public extension DispatchQueue {

  private class func delay(delay: TimeInterval, closure: @escaping () -> Void) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
  }

  class func performAction(after seconds: TimeInterval, callBack: @escaping (() -> Void) ) {
    DispatchQueue.delay(delay: seconds) {
      callBack()
    }
  }

}

Utilizzare come segue.

DispatchQueue.performAction(after: 0.3) {
  // Code Here
}

1

Ritardare la chiamata GCD utilizzando asyncAfter in rapido

let delayQueue = DispatchQueue(label: "com.theappmaker.in", qos: .userInitiated)
let additionalTime: DispatchTimeInterval = .seconds(2)

Possiamo ritardare di ** microsecondi , millisecondi , nanosecondi

delayQueue.asyncAfter(deadline: .now() + 0.60) {
    print(Date())
}

delayQueue.asyncAfter(deadline: .now() + additionalTime) {
    print(Date())
}

1

In Swift 4

Usa questo frammento:

    let delayInSec = 1.0
    DispatchQueue.main.asyncAfter(deadline: .now() + delayInSec) {
       // code here
       print("It works")
    }

Questo è già in altre risposte (brindy, per esempio, o di Rahul) ... stessa sintassi ...
Eric Aya,

1
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // ...
});

La dispatch_after(_:_:_:)funzione accetta tre parametri:

un ritardo di
una spedizione in coda
un blocco o una chiusura

La dispatch_after(_:_:_:)funzione richiama il blocco o la chiusura sulla coda di invio che viene passata alla funzione dopo un determinato ritardo. Si noti che il ritardo viene creato utilizzando ildispatch_time(_:_:) funzione. Ricordalo perché utilizziamo anche questa funzione in Swift.

Consiglio di passare attraverso il tutorial Tutorial di spedizione Raywenderlich


1

In Swift 5, utilizza quanto segue:

 DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: closure) 

// time gap, specify unit is second
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
            Singleton.shared().printDate()
        }
// default time gap is second, you can reduce it
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
          // just do it!
    }

0

utilizzare questo codice per eseguire alcune attività correlate all'interfaccia utente dopo 2,0 secondi.

            let delay = 2.0
            let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
            let mainQueue = dispatch_get_main_queue()

            dispatch_after(delayInNanoSeconds, mainQueue, {

                print("Some UI related task after delay")
            })

Versione Swift 3.0

Dopo la funzione di chiusura, eseguire alcune attività dopo un ritardo sul thread principale.

func performAfterDelay(delay : Double, onCompletion: @escaping() -> Void){

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: {
       onCompletion()
    })
}

Chiamare questa funzione come:

performAfterDelay(delay: 4.0) {
  print("test")
}

1
Questo è quasi identico alle risposte precedenti
Daniel Galasko,

Sembra che questa risposta sia stata fatta all'inizio del 2016 ed è più vecchia di almeno altre 6 risposte ..
eharo2

0

Ora più che zucchero sintattico per spedizioni asincrone in Grand Central Dispatch (GCD) in Swift.

aggiungi Podfile

pod 'AsyncSwift'

Quindi, puoi usarlo in questo modo.

let seconds = 3.0
Async.main(after: seconds) {
print("Is called after 3 seconds")
}.background(after: 6.0) {
print("At least 3.0 seconds after previous block, and 6.0 after Async code is called")
}

Apple ci ha dato tutto il necessario per utilizzare GCD in poche righe. Perché preoccuparsi di pod, spazio di lavoro e così via? Leggi semplicemente i documenti su @escaping e acquisizione. è abbastanza.
Ingconti,

0

Swift 4 ha un modo piuttosto breve di farlo:

Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
    // Your stuff here
    print("hello")
}

0

Ecco la versione sincrona di asyncAfter in Swift:

let deadline = DispatchTime.now() + .seconds(3)
let semaphore = DispatchSemaphore.init(value: 0)
DispatchQueue.global().asyncAfter(deadline: deadline) {
    dispatchPrecondition(condition: .onQueue(DispatchQueue.global()))
    semaphore.signal()
}

semaphore.wait()

Insieme a quello asincrono:

let deadline = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadline) {
    dispatchPrecondition(condition: .onQueue(DispatchQueue.global()))
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.