Comment créer un retard dans Swift?


Réponses:


271

Au lieu d'un sommeil, qui verrouillera votre programme s'il est appelé à partir du thread d'interface utilisateur, envisagez d'utiliser NSTimerou un temporisateur de répartition.

Mais, si vous avez vraiment besoin d'un délai dans le thread actuel:

do {
    sleep(4)
}

Cela utilise la sleepfonction d'UNIX.


4
dormir fonctionne sans importer darwin, je ne sais pas si c'est un changement
Dan Beaulieu

17
usleep () fonctionne aussi si vous avez besoin de quelque chose de plus précis qu'une résolution de 1 seconde.
prewett

18
usleep () prend des millionièmes de seconde, donc usleep (1000000) dormira pendant 1 sec
Harris

40
Merci d'avoir gardé le préambule "ne faites pas ça" court.
Dan Rosenstark

2
J'utilise sleep () pour simuler des processus d'arrière-plan de longue durée.
William T. Mallard,

345

Dans la dispatch_afterplupart des cas, l' utilisation d'un bloc est préférable à l'utilisation, sleep(time)car le thread sur lequel le sommeil est effectué est empêché d'effectuer d'autres travaux. lorsque vous utilisez dispatch_afterle thread sur lequel vous travaillez, il n'est pas bloqué, il peut donc effectuer d'autres tâches en attendant.
Si vous travaillez sur le thread principal de votre application, l'utilisation sleep(time)est mauvaise pour l'expérience utilisateur de votre application car l'interface utilisateur ne répond pas pendant ce temps.

Dispatch after planifie l'exécution d'un bloc de code au lieu de geler le thread:

Swift ≥ 3,0

let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
    // Put your code which should be executed with a delay here
}

Rapide <3,0

let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
    // Put your code which should be executed with a delay here
}

1
Et les actions périodiques?
qed

1
il existe des possibilités d'utiliser gcd pour des tâches périodiques décrites dans cet article de la documentation du développeur Apple, mais je recommanderais d'utiliser un NSTimer pour cela. cette réponse montre comment faire exactement cela en un rien de temps.
Palle

2
Code mis à jour pour Xcode 8.2.1. .now() + .seconds(4)Donne précédemment une erreur:expression type is ambiguous without more context
richards

Comment puis-je suspendre la fonction invoquée de la file d'attente? @Palle
Mukul More

14
Vous n'avez plus besoin de l'ajouter, .now() + .seconds(5)c'est tout simplement.now() + 5
cnzac

45

Comparaison entre différentes approches dans swift 3.0

1. Dormir

Cette méthode n'a pas de rappel. Mettez les codes directement après cette ligne pour les exécuter en 4 secondes. Il empêchera l'utilisateur d'itérer avec des éléments d'interface utilisateur comme le bouton de test jusqu'à ce que le temps soit écoulé. Bien que le bouton soit gelé lorsque le sommeil entre en action, d'autres éléments comme l'indicateur d'activité tournent toujours sans geler. Vous ne pouvez plus déclencher cette action pendant le sommeil.

sleep(4)
print("done")//Do stuff here

entrez la description de l'image ici

2. Envoi, exécution et minuterie

Ces trois méthodes fonctionnent de manière similaire, elles s'exécutent toutes sur le thread d'arrière-plan avec des rappels, juste avec une syntaxe différente et des fonctionnalités légèrement différentes.

La répartition est couramment utilisée pour exécuter quelque chose sur le thread d'arrière-plan. Il a le rappel dans le cadre de l'appel de fonction

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
    print("done")
})

Perform est en fait une minuterie simplifiée. Il met en place une minuterie avec le retard, puis déclenche la fonction par sélecteur.

perform(#selector(callback), with: nil, afterDelay: 4.0)

func callback() {
    print("done")
}}

Et enfin, le minuteur permet également de répéter le rappel, ce qui n'est pas utile dans ce cas

Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(callback), userInfo: nil, repeats: false)


func callback() {
    print("done")
}}

Pour ces trois méthodes, lorsque vous cliquez sur le bouton pour les déclencher, l'interface utilisateur ne se fige pas et vous êtes autorisé à cliquer à nouveau dessus. Si vous cliquez à nouveau sur le bouton, une autre minuterie est configurée et le rappel sera déclenché deux fois.

entrez la description de l'image ici

En conclusion

Aucune des quatre méthodes ne fonctionne assez bien seule. sleepdésactivera l'interaction avec l'utilisateur, de sorte que l'écran "se fige " (pas réellement) et entraîne une mauvaise expérience utilisateur. Les trois autres méthodes ne gèleront pas l'écran, mais vous pouvez les déclencher plusieurs fois, et la plupart du temps, vous voulez attendre que vous récupériez l'appel avant de permettre à l'utilisateur de refaire l'appel.

Ainsi, une meilleure conception utilisera l'une des trois méthodes asynchrones avec blocage d'écran. Lorsque l'utilisateur clique sur le bouton, couvrez tout l'écran avec une vue translucide avec un indicateur d'activité de rotation sur le dessus, indiquant à l'utilisateur que le clic sur le bouton est en cours de traitement. Ensuite, supprimez la vue et l'indicateur dans la fonction de rappel, indiquant à l'utilisateur que l'action est correctement gérée, etc.


1
Notez que dans Swift 4, avec l'inférence objc désactivée, vous devrez ajouter @objcdevant la fonction de rappel pour l' perform(...)option. Comme ça:@objc func callback() {
leanne

32

Je suis d'accord avec Palle que l'utilisation dispatch_afterest un bon choix ici. Mais vous n'aimez probablement pas les appels GCD car ils sont assez ennuyeux à écrire . Au lieu de cela, vous pouvez ajouter cette aide pratique :

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 un fil d'arrière-plan comme celui-ci:

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

Retarder le code sur le thread principal est encore plus simple:

delay(bySeconds: 1.5) { 
    // delayed code, by default run in main 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 ou Accio puis l'utiliser exactement comme dans les exemples ci-dessus:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}

Cela semble très utile. Cela vous dérange de le mettre à jour avec une version 3.0 rapide. Merci
grahamcracker1234

J'ai commencé à migrer HandySwift vers Swift 3 et je prévois de publier une nouvelle version la semaine prochaine. Ensuite, je vais également mettre à jour le script ci-dessus. Restez à l'écoute.
Jeehut

La migration de HandySwift vers Swift 3 est maintenant terminée et publiée dans la version 1.3.0. Je viens également de mettre à jour le code ci-dessus pour travailler avec Swift 3! :)
Jeehut

1
Pourquoi multipliez-vous par NSEC_PER_SEC puis divisez-vous à nouveau? N'est-ce pas redondant? (par exemple Double (Int64 (secondes * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) Ne pourriez-vous pas simplement écrire «DispatchTime.now () + Double (secondes)?
Mark A. Donohoe

1
Merci @MarqueIV, vous avez bien sûr raison. Je viens de mettre à jour le code. C'était simplement le résultat de la conversion automatique de Xcode 8 et la conversion de type était nécessaire avant car elle DispatchTimen'était pas disponible dans Swift 2. C'était let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))avant.
Jeehut

24

Vous pouvez également le faire avec Swift 3.

Exécutez la fonction après un délai comme ceci.

override func viewDidLoad() {
    super.viewDidLoad()

    self.perform(#selector(ClassName.performAction), with: nil, afterDelay: 2.0)
}


     @objc func performAction() {
//This function will perform after 2 seconds
            print("Delayed")
        }

23

Dans Swift 4.2 et Xcode 10.1

Vous avez au total 4 façons de retarder. Parmi ces options, il est préférable d'appeler ou d'exécuter une fonction après un certain temps. Le sleep () est le moins utilisé.

Option 1.

DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    self.yourFuncHere()
}
//Your function here    
func yourFuncHere() {

}

Option 2.

perform(#selector(yourFuncHere2), with: nil, afterDelay: 5.0)

//Your function here  
@objc func yourFuncHere2() {
    print("this is...")
}

Option 3.

Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(yourFuncHere3), userInfo: nil, repeats: false)

//Your function here  
@objc func yourFuncHere3() {

}

Option 4.

sleep(5)

Si vous voulez appeler une fonction après un certain temps pour exécuter quelque chose, n'utilisez pas sleep .


19

NSTimer

La réponse de @nneonneo a suggéré d'utiliser NSTimermais n'a pas montré comment le faire. Voici la syntaxe de base:

let delay = 0.5 // time in seconds
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(myFunctionName), userInfo: nil, repeats: false)

Voici un projet très simple pour montrer comment il pourrait être utilisé. Lorsqu'une touche est enfoncée, elle démarre une minuterie qui appelle une fonction après un délai d'une demi-seconde.

import UIKit
class ViewController: UIViewController {

    var timer = NSTimer()
    let delay = 0.5
    
    // start timer when button is tapped
    @IBAction func startTimerButtonTapped(sender: UIButton) {

        // cancel the timer in case the button is tapped multiple times
        timer.invalidate()

        // start the timer
        timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
    }

    // function to be called after the delay
    func delayedAction() {
        print("action has started")
    }
}

L'utilisation dispatch_time(comme dans la réponse de Palle ) est une autre option valable. Cependant, il est difficile d'annuler . Avec NSTimer, pour annuler un événement différé avant qu'il ne se produise, il vous suffit d'appeler

timer.invalidate()

L'utilisation sleepn'est pas recommandée, en particulier sur le thread principal, car elle empêche tout travail en cours sur le thread.

Voir ici pour ma réponse plus complète.


7

Essayez l'implémentation suivante dans Swift 3.0

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

Usage

delayWithSeconds(1) {
   //Do something
}

7

Vous pouvez créer une extension pour utiliser facilement la fonction de retard (Syntaxe: Swift 4.2+)

extension UIViewController {
    func delay(_ delay:Double, closure:@escaping ()->()) {
        DispatchQueue.main.asyncAfter(
            deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
    }
}

Comment utiliser dans UIViewController

self.delay(0.1, closure: {
   //execute code
})

6

Si vous devez définir un délai inférieur à une seconde, il n'est pas nécessaire de définir le paramètre .seconds. J'espère que cela est utile à quelqu'un.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})

5
DispatchQueue.global(qos: .background).async {
    sleep(4)
    print("Active after 4 sec, and doesn't block main")
    DispatchQueue.main.async{
        //do stuff in the main thread here
    }
}

4
Faire DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}est probablement plus correct ✌️
eonist

5

Si votre code s'exécute déjà dans un thread d'arrière-plan, mettez le thread en pause à l'aide de cette méthode dans Foundation :Thread.sleep(forTimeInterval:)

Par exemple:

DispatchQueue.global(qos: .userInitiated).async {

    // Code is running in a background thread already so it is safe to sleep
    Thread.sleep(forTimeInterval: 4.0)
}

(Voir d'autres réponses pour des suggestions lorsque votre code s'exécute sur le thread principal.)


3

Pour créer un délai simple, vous pouvez importer Darwin, puis utiliser la mise en veille (secondes) pour effectuer le délai. Cela ne prend que des secondes entières, cependant, pour des mesures plus précises, vous pouvez importer Darwin et utiliser l'usep (millionièmes de seconde) pour une mesure très précise. Pour tester cela, j'ai écrit:

import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")

Qui imprime, puis attend 1 seconde et imprime, puis attend 0,4 seconde puis imprime. Tout a fonctionné comme prévu.


-3

c'est le plus simple

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })

2
Cela ne fait pas partie de la Fondation, il est, comme je suppose, inclus dans le Cocoapod «HandySwift». Vous devriez clarifier cela dans votre réponse.
Jan Nash

Swift a-t-il quelque chose qui attend que quelque chose se produise ou non?
Saeed Rahmatolahi
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.