dispatch_after - GCD dans Swift?

559

Je suis passé par l' iBook d'Apple et trouvé aucune définition de celui-ci:

Quelqu'un peut-il expliquer la structure de dispatch_after?

dispatch_after(<#when: dispatch_time_t#>, <#queue: dispatch_queue_t?#>, <#block: dispatch_block_t?#>)
Kumar KL
la source
1
Apple n'a pas publié ce livre en 2018. Les dernières archives que j'ai pu trouver datent de décembre 2017 . Les anciens liens vers l'iBook sont désormais simplement redirigés vers developer.apple.com/documentation/swift .
Cœur

Réponses:

742

Une idée plus claire de la structure:

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

dispatch_time_test un UInt64. Le dispatch_queue_test en fait un alias de type pour un NSObject, mais vous devez simplement utiliser vos méthodes GCD familières pour obtenir les files d'attente. Le bloc est une fermeture Swift. Plus précisément, dispatch_block_test défini comme () -> Void, ce qui équivaut à () -> ().

Exemple d'utilisation:

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

ÉDITER:

Je recommande d'utiliser la delayfonction vraiment sympa de @ matt .

EDIT 2:

Dans Swift 3, il y aura de nouveaux wrappers pour GCD. Vois ici: https://github.com/apple/swift-evolution/blob/master/proposals/0088-libdispatch-for-swift3.md

L'exemple d'origine serait écrit comme suit dans Swift 3:

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

Notez que vous pouvez écrire la deadlineTimedéclaration en tant que DispatchTime.now() + 1.0et obtenir le même résultat car l' +opérateur est remplacé comme suit (de même pour -):

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

Cela signifie que si vous n'utilisez pas le DispatchTimeInterval enumet écrivez simplement un nombre, on suppose que vous utilisez des secondes.

Cezary Wojcik
la source
17
Astuce: le bloc étant le dernier paramètre de la fonction, vous pouvez utiliser la syntaxe de «fermeture arrière» de Swift pour plus de lisibilité:dispatch_after(1, dispatch_get_main_queue()) { println("test") }
Bill
8
Je pense en utilisant le numéro 1de dispatch_after(1, ...peut causer beaucoup de confusion. Les gens penseront qu'il s'agit d'un nombre de secondes, alors qu'il s'agit en fait de nano-secondes . Je suggère de voir la réponse de @brindy sur la façon de créer correctement ce numéro.
Hlung
3
Veuillez changer 1en dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))car cela crée de la confusion. Les gens pourraient penser que vous n'avez pas besoin de créer un dispatch_time_t dans Swift
OemerA
4
La version Swift 3 ne semble pas fonctionner. Il se plaint que Binary operator '+' cannot be applied to operands of type DispatchTime and '_'sur la lignelet delayTime = DispatchTime.now() + .seconds(1.0)
Andy Ibanez
9
La réécriture DispatchTime.now() + 1.0semble être le seul moyen de le faire fonctionner (pas besoin .seconds)
Andy Ibanez
1093

J'utilise dispatch_aftersi souvent que j'ai écrit une fonction utilitaire de haut niveau pour simplifier la syntaxe:

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

Et maintenant, vous pouvez parler comme ceci:

delay(0.4) {
    // do stuff
}

Wow, une langue où vous pouvez améliorer la langue. Qu'est-ce qui pourrait être mieux?


Mise à jour pour Swift 3, Xcode 8 Seed 6

Cela ne vaut presque pas la peine d'être dérangé, maintenant qu'ils ont amélioré la syntaxe d'appel:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
mat
la source
2
J'avais juste besoin d'un raccourci pour le calcul du retard, je me suis retrouvé avec:func delayInSec(delay: Double) -> dispatch_time_t { return dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))) }
Aviel Gross
4
@ agf119105 Si vous n'avez qu'une seule ligne de code dans la fermeture, ajoutez une autre ligne de code (par exemple return).
mat
2
@GastonM Irrelevant. Passer une fonction n'a en soi aucun problème de gestion de la mémoire.
mat du
7
"Une langue où vous pouvez améliorer la langue". Je ne comprends pas comment la définition d'une fonction globale améliore le langage, ni pourquoi cela n'est même pas faisable en C même. Peut-être que si vous surchargez un opérateur;)1.0 ~~ { code...}
Yerk
8
Ne remettant pas en cause l'exactitude de votre réponse, mais "n'utilise-je pas dispatch_after si souvent" n'est-il pas une odeur de code qui serait mieux combattue en ne fournissant pas de fonction de commodité?
Nikolai Ruhe
128

Swift 3+

C'est super facile et élégant dans Swift 3+:

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

Réponse plus ancienne:

Pour développer la réponse de Cezary, qui s'exécutera après 1 nanoseconde, j'ai dû faire ce qui suit pour l'exécuter après 4 secondes et demie.

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)

Edit: j'ai découvert que mon code d'origine était légèrement faux. Le typage implicite provoque une erreur de compilation si vous ne transtypez pas NSEC_PER_SEC en Double.

Si quelqu'un peut suggérer une solution plus optimale, je serais ravi de l'entendre.

brindy
la source
J'obtiens une erreur de compilation pour une API obsolète avec dispatch_get_current_queue(). J'ai utilisé à la dispatch_get_main_queue()place.
David L
@DavidL - merci, dispatch_get_main_queue()c'est certainement ce que vous devriez utiliser. Mettra à jour.
brindy
j'ai essayé cela dans un terrain de jeu avec swift 3 et cela ne fonctionne pas
μολὼν.λαβέ
@GAlexander fonctionne pour moi. Autorisez-vous le terrain de jeu à s'exécuter indéfiniment?
brindy
euh, eh bien non, je laisse courir pendant quelques heures et toujours rien imprimé. voici ce que j'ai utilisé. "import Dispatch import Darwin import CoreGraphics 'DispatchQueue.main.asyncAfter (délai: .now () + 4.5) {print (" got here ")}"
μολὼν.λαβέ
83

La syntaxe de matt est très agréable et si vous devez invalider le bloc, vous pouvez utiliser ceci:

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

Utilisez comme suit

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

crédits

Le lien ci-dessus semble être en panne. Code Objc original de Github

Waam
la source
1
La seule fonctionnalité de performance qui a performSelector: afterDelay est la possibilité de l'annuler. Seule cette solution couvre le problème. Merci
HotJard
@HotJard Notez qu'il performSelector:afterDelay:est désormais disponible dans Swift 2, vous pouvez donc l'annuler.
mat
@matt mais il n'est disponible que pour NSObject, n'est-ce pas?
HotJard
@HotJard Bien sûr, mais c'est mieux que de ne pas l'avoir du tout. Je n'y vois aucun problème. Cependant, tout comme avec cette réponse, j'avais déjà compensé sa perte en écrivant un temporisateur annulable basé sur GCD (en utilisant un dispatch_source_t, car c'est quelque chose que vous pouvez annuler).
mat
2
Merci beaucoup, je l'ai utilisé jusqu'à Swift 2.3. Le compilateur Swift 3.0 se plaint maintenant, ce serait génial si vous mettiez à jour votre réponse!
automatique à 12h04
27

Solution la plus simple dans Swift 3.0 & Swift 4.0 & Swift 5.0

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

Usage

delayWithSeconds(1) {
   //Do something
}
Vakas
la source
22

Apple a un extrait dispatch_after pour 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#>
});

Voici le même extrait porté sur Swift 3:

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

Une autre façon est d'étendre Double comme ceci:

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

Ensuite, vous pouvez l'utiliser comme ceci:

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

J'aime la fonction de retard de matt mais juste par préférence, je préfère limiter les fermetures de passage.

garafajon
la source
8

Dans Swift 3.0

Files d'attente de répartition

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

Envoi après 5 secondes

    DispatchQueue.main.after(when: DispatchTime.now() + 5) {
        print("Dispatch after 5 sec")
    }
Mohammad Sadiq Shaikh
la source
4

Version Swift 3.0

La fonction de fermeture suivante exécute une tâche après un délai sur le thread principal.

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

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

Appelez cette fonction comme:

performAfterDelay(delay: 4.0) {
  print("test")
}
Himanshu Mahajan
la source
4

1) Ajoutez cette méthode dans le cadre de l'extension 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)
    }
}

Appelez cette méthode sur 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
})

// Forme compacte

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue()) {
    //Code here
 }
}
AG
la source
3

Bien qu'il ne s'agisse pas de la question d'origine du PO, certaines NSTimerquestions connexes ont été marquées comme des doublons de cette question, il vaut donc la peine d'y inclure une NSTimerréponse ici.

NSTimer contre dispatch_after

  • NSTimerest de niveau plus élevé tandis que dispatch_afterest de niveau plus bas.
  • NSTimerest plus facile à annuler. L'annulation dispatch_afternécessite l'écriture de plus de code .

Retarder une tâche avec NSTimer

Créez une NSTimerinstance.

var timer = NSTimer()

Démarrez la minuterie avec le retard dont vous avez besoin.

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

Ajoutez une fonction à appeler après le délai (en utilisant le nom que vous avez utilisé pour le selectorparamètre ci-dessus).

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

Remarques

  • Si vous devez annuler l'action avant qu'elle ne se produise, appelez simplement timer.invalidate().
  • Pour une action répétée, utilisez repeats: true.
  • Si vous avez un événement ponctuel sans avoir besoin d'annuler, il n'est pas nécessaire de créer la timervariable d'instance. Les éléments suivants suffiront:

    NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 
  • Voir ma réponse plus complète ici .

Suragch
la source
3

Pour plusieurs fonctions, utilisez ceci. Ceci est très utile pour utiliser des animations ou un chargeur d'activité pour les fonctions statiques ou toute mise à jour de l'interface utilisateur.

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

Par exemple - Utilisez une animation avant un rechargement de tableView. Ou toute autre mise à jour de l'interface utilisateur après l'animation.

*// 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()
                }
            }
Rahul Singha Roy
la source
2

Cela a fonctionné pour moi.

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

Objectif 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);
});
Garçon Ankit
la source
2

Swift 3 & 4:

Vous pouvez créer une extension sur DispatchQueue et ajouter un retard de fonction qui utilise la fonction asyncAfter de DispatchQueue en interne

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

utilisation:

DispatchQueue.delay(.seconds(1)) {
    print("This is after delay")
}
Suhit Patil
la source
1

Un autre assistant pour retarder votre code qui est 100% Swift dans l'utilisation et permet éventuellement de choisir un thread différent pour exécuter votre code retardé à partir de:

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

Maintenant, vous retardez simplement votre code sur le fil principal comme ceci:

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

Si vous souhaitez retarder votre code sur un autre thread :

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

Si vous préférez un Framework qui possède également des fonctionnalités plus pratiques, passez à HandySwift . Vous pouvez l'ajouter à votre projet via Carthage puis l'utiliser exactement comme dans les exemples ci-dessus, par exemple:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}
Jeehut
la source
1

Je préfère toujours utiliser l'extension au lieu des fonctions gratuites.

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

}

Utilisez comme suit.

DispatchQueue.performAction(after: 0.3) {
  // Code Here
}
Hardeep Singh
la source
1

Retarder l'appel GCD en utilisant asyncAfter dans swift

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

Nous pouvons retarder comme ** microsecondes , millisecondes , nanosecondes

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

delayQueue.asyncAfter(deadline: .now() + additionalTime) {
    print(Date())
}
Sanjay Mali
la source
1

Dans Swift 4

Utilisez cet extrait:

    let delayInSec = 1.0
    DispatchQueue.main.asyncAfter(deadline: .now() + delayInSec) {
       // code here
       print("It works")
    }
Roche noire
la source
C'est déjà dans d'autres réponses (brindy, par exemple, ou Rahul) ... même syntaxe ...
Eric Aya
1
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // ...
});

La dispatch_after(_:_:_:)fonction prend trois paramètres:

un retard
une file d'attente de répartition
un bloc ou une fermeture

La dispatch_after(_:_:_:)fonction appelle le bloc ou la fermeture de la file d'attente de répartition qui est passé à la fonction après un délai donné. Notez que le retard est créé à l'aide de la dispatch_time(_:_:)fonction. N'oubliez pas ceci car nous utilisons également cette fonction dans Swift.

Je recommande de suivre le tutoriel Raywenderlich Dispatch tutorial

CrazyPro007
la source
1

Dans Swift 5, utilisez ce qui suit:

 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!
    }
Zgpeace
la source
0

utilisez ce code pour effectuer une tâche liée à l'interface utilisateur après 2,0 secondes.

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

Version Swift 3.0

La fonction de fermeture suivante exécute une tâche après un délai sur le thread principal.

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

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

Appelez cette fonction comme:

performAfterDelay(delay: 4.0) {
  print("test")
}
Himanshu Mahajan
la source
1
Ceci est presque identique aux réponses précédentes
Daniel Galasko
Il semble que cette réponse ait été faite début 2016, et soit plus ancienne qu'au moins 6 autres réponses.
eharo2
0

Maintenant plus que du sucre syntaxique pour les envois asynchrones dans Grand Central Dispatch (GCD) dans Swift.

ajouter Podfile

pod 'AsyncSwift'

Ensuite, vous pouvez l'utiliser comme ça.

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")
}
Tim
la source
Apple nous a donné tout le nécessaire pour utiliser GCD en quelques lignes. Pourquoi s'embêter avec les pods, l'espace de travail, etc.? Lisez simplement les documents sur @escaping et la capture. c'est assez.
ingconti
0

Swift 4 a un moyen assez court de le faire:

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

Voici la version synchrone de asyncAfter dans 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()

Avec un asynchrone:

let deadline = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadline) {
    dispatchPrecondition(condition: .onQueue(DispatchQueue.global()))
}
Maxim Makhun
la source