Wie erstelle ich eine Verzögerung in Swift?

260

Ich möchte meine App an einem bestimmten Punkt anhalten. Mit anderen Worten, ich möchte, dass meine App den Code ausführt, aber dann an einem bestimmten Punkt 4 Sekunden lang pausiert und dann mit dem Rest des Codes fortfährt. Wie kann ich das machen?

Ich benutze Swift.

Schuey999
quelle

Antworten:

269

Anstelle eines Ruhezustands, der Ihr Programm blockiert, wenn es vom UI-Thread aufgerufen wird, sollten Sie NSTimereinen Versand-Timer verwenden.

Aber wenn Sie wirklich eine Verzögerung im aktuellen Thread benötigen:

do {
    sleep(4)
}

Dies verwendet die sleepFunktion von UNIX.

nneonneo
quelle
4
Schlaf funktioniert ohne Darwin zu importieren, nicht sicher, ob dies eine Änderung ist
Dan Beaulieu
16
usleep () funktioniert auch, wenn Sie eine genauere Auflösung als 1 Sek. benötigen.
Prewett
17
usleep () dauert eine Millionstelsekunde, so dass usleep (1000000) 1 Sekunde lang schläft
Harris
39
Vielen Dank, dass Sie die Präambel "Mach das nicht" kurz gehalten haben.
Dan Rosenstark
2
Ich benutze sleep (), um lang laufende Hintergrundprozesse zu simulieren.
William T. Mallard
344

Die Verwendung eines dispatch_afterBlocks ist in den meisten Fällen besser als die Verwendung, sleep(time)da der Thread, für den der Schlaf ausgeführt wird, für andere Arbeiten gesperrt ist. Wenn Sie dispatch_afterden Thread verwenden, an dem gearbeitet wird, wird er nicht blockiert, sodass er in der Zwischenzeit andere Arbeiten ausführen kann.
Wenn Sie am Hauptthread Ihrer Anwendung arbeiten, ist die Verwendung sleep(time)für die Benutzererfahrung Ihrer App schlecht, da die Benutzeroberfläche während dieser Zeit nicht reagiert.

Versand nach Zeitplan der Ausführung eines Codeblocks, anstatt den Thread einzufrieren:

Swift ≥ 3.0

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

Swift <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
}
Palle
quelle
1
Was ist mit regelmäßigen Aktionen?
Qed
1
Es gibt Möglichkeiten, gcd für regelmäßige Aufgaben zu verwenden, die in diesem Artikel in der Apple-Entwicklerdokumentation beschrieben werden. Ich würde jedoch empfehlen, dafür einen NSTimer zu verwenden. Diese Antwort zeigt, wie man genau das schnell macht.
Palle
2
Code aktualisiert für Xcode 8.2.1. Zuvor .now() + .seconds(4)gibt Fehler:expression type is ambiguous without more context
Richards
Wie kann ich die aus der Warteschlange aufgerufene Funktion aussetzen? @Palle
Mukul More
14
Sie müssen es nicht mehr hinzufügen, .now() + .seconds(5)es ist einfach.now() + 5
cnzac
45

Vergleich zwischen verschiedenen Ansätzen in Swift 3.0

1. Schlaf

Diese Methode hat keinen Rückruf. Setzen Sie Codes direkt hinter diese Zeile, um sie in 4 Sekunden auszuführen. Es verhindert, dass Benutzer mit UI-Elementen wie der Testschaltfläche iterieren, bis die Zeit abgelaufen ist. Obwohl die Taste beim Einsetzen des Schlafes eingefroren ist, drehen sich andere Elemente wie die Aktivitätsanzeige immer noch, ohne einzufrieren. Sie können diese Aktion im Ruhezustand nicht erneut auslösen.

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

Geben Sie hier die Bildbeschreibung ein

2. Dispatch, Perform und Timer

Diese drei Methoden funktionieren ähnlich. Sie werden alle im Hintergrund-Thread mit Rückrufen ausgeführt, nur mit unterschiedlicher Syntax und leicht unterschiedlichen Funktionen.

Der Versand wird häufig verwendet, um etwas im Hintergrund-Thread auszuführen. Es hat den Rückruf als Teil des Funktionsaufrufs

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

Perform ist eigentlich ein vereinfachter Timer. Es stellt einen Timer mit der Verzögerung ein und löst dann die Funktion per Selektor aus.

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

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

Und schließlich bietet der Timer auch die Möglichkeit, den Rückruf zu wiederholen, was in diesem Fall nicht sinnvoll ist

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


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

Wenn Sie bei all diesen drei Methoden auf die Schaltfläche klicken, um sie auszulösen, friert die Benutzeroberfläche nicht ein und Sie können erneut darauf klicken. Wenn Sie erneut auf die Schaltfläche klicken, wird ein anderer Timer eingerichtet und der Rückruf wird zweimal ausgelöst.

Geben Sie hier die Bildbeschreibung ein

Abschließend

Keine der vier Methoden funktioniert allein gut genug. sleepDeaktiviert die Benutzerinteraktion, sodass der Bildschirm (nicht wirklich) " einfriert " und zu einer schlechten Benutzererfahrung führt. Die anderen drei Methoden frieren den Bildschirm nicht ein, aber Sie können sie mehrmals auslösen. In den meisten Fällen möchten Sie warten, bis Sie den Anruf zurückerhalten, bevor Sie dem Benutzer erlauben, den Anruf erneut zu tätigen.

Ein besseres Design wird also eine der drei asynchronen Methoden mit Bildschirmblockierung verwenden. Wenn der Benutzer auf die Schaltfläche klickt, decken Sie den gesamten Bildschirm mit einer durchscheinenden Ansicht ab, auf der sich eine rotierende Aktivitätsanzeige befindet, die dem Benutzer mitteilt, dass das Klicken auf die Schaltfläche ausgeführt wird. Entfernen Sie dann die Ansicht und die Anzeige in der Rückruffunktion, um dem Benutzer mitzuteilen, dass die Aktion ordnungsgemäß ausgeführt wurde usw.

Fangming
quelle
1
Beachten Sie, dass Sie in Swift 4 bei deaktivierter Objektinferenz @objcvor der Rückruffunktion die perform(...)Option hinzufügen müssen . Wie so:@objc func callback() {
Leanne
32

Ich stimme Palle zu, dass die Verwendung dispatch_afterhier eine gute Wahl ist. Aber Sie mögen die GCD-Aufrufe wahrscheinlich nicht, da das Schreiben ziemlich nervig ist . Stattdessen können Sie diesen praktischen Helfer hinzufügen :

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

Jetzt verzögern Sie einfach Ihren Code in einem Hintergrund-Thread wie diesem:

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

Das Verzögern des Codes im Hauptthread ist noch einfacher:

delay(bySeconds: 1.5) { 
    // delayed code, by default run in main thread
}

Wenn Sie ein Framework bevorzugen , das auch einige weitere praktische Funktionen bietet, lesen Sie HandySwift . Sie können es über Karthago oder Accio zu Ihrem Projekt hinzufügen und dann genau wie in den obigen Beispielen verwenden:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}
Jeehut
quelle
Dies scheint sehr hilfreich zu sein. Haben Sie etwas dagegen, es mit einer schnellen 3.0-Version zu aktualisieren? Danke
Grahamcracker1234
Ich habe mit der Migration von HandySwift auf Swift 3 begonnen und plane, nächste Woche eine neue Version zu veröffentlichen. Dann werde ich auch das obige Skript aktualisieren. Bleib dran.
Jeehut
Die Migration von HandySwift zu Swift 3 ist jetzt abgeschlossen und in Version 1.3.0 veröffentlicht. Ich habe auch gerade den obigen Code aktualisiert, um mit Swift 3 zu arbeiten! :)
Jeehut
1
Warum multiplizieren Sie mit NSEC_PER_SEC und dividieren dann erneut? Ist das nicht überflüssig? (zB Double (Int64 (Sekunden * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) Könnten Sie nicht einfach 'DispatchTime.now () + Double (Sekunden) schreiben?
Mark A. Donohoe
1
Danke @MarqueIV, du hast natürlich recht. Ich habe gerade den Code aktualisiert. Es war einfach das Ergebnis der automatischen Konvertierung von Xcode 8 und die Typkonvertierung war zuvor erforderlich, da DispatchTimesie in Swift 2 nicht verfügbar war let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))).
Jeehut
24

Sie können dies auch mit Swift 3 tun.

Führen Sie die Funktion nach einer Verzögerung wie folgt aus.

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")
        }
Cyril
quelle
23

In Swift 4.2 und Xcode 10.1

Sie haben insgesamt 4 Möglichkeiten zu verzögern. Von diesen Optionen ist es vorzuziehen, nach einiger Zeit eine Funktion aufzurufen oder auszuführen. Der Schlaf () ist mindestens Fall gebräuchlich.

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)

Wenn Sie nach einiger Zeit eine Funktion aufrufen möchten, um etwas auszuführen, verwenden Sie nicht sleep .

iOS
quelle
19

NSTimer

Die Antwort von @nneonneo schlug vor, zu verwenden NSTimer, zeigte aber nicht, wie es geht. Dies ist die grundlegende Syntax:

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

Hier ist ein sehr einfaches Projekt, um zu zeigen, wie es verwendet werden kann. Wenn eine Taste gedrückt wird, wird ein Timer gestartet, der nach einer Verzögerung von einer halben Sekunde eine Funktion aufruft.

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

Die Verwendung dispatch_time(wie in Palles Antwort ) ist eine weitere gültige Option. Es ist jedoch schwer abzubrechen . Mit NSTimer, um ein verzögertes Ereignis abzubrechen, bevor es eintritt, müssen Sie nur anrufen

timer.invalidate()

Die Verwendung sleepwird nicht empfohlen, insbesondere für den Haupt-Thread, da dadurch die gesamte Arbeit am Thread gestoppt wird.

Siehe hier für meine ausführlichere Antwort.

Suragch
quelle
7

Versuchen Sie die folgende Implementierung in Swift 3.0

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

Verwendung

delayWithSeconds(1) {
   //Do something
}
Vakas
quelle
7

Sie können eine Erweiterung erstellen, um die Verzögerungsfunktion einfach zu verwenden (Syntax: 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)
    }
}

Verwendung in UIViewController

self.delay(0.1, closure: {
   //execute code
})
Hardik Thakkar
quelle
6

Wenn Sie eine Verzögerung von weniger als einer Sekunde einstellen müssen, muss der Parameter .seconds nicht festgelegt werden. Ich hoffe, das ist jemandem nützlich.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})
Booharin
quelle
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
    }
}
Eonist
quelle
4
Tun DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}ist wahrscheinlich korrekter e
Eonist
5

Wenn Ihr Code bereits in einem Hintergrundthread ausgeführt wird, halten Sie den Thread mit dieser Methode in Foundation an :Thread.sleep(forTimeInterval:)

Beispielsweise:

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

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

(In anderen Antworten finden Sie Vorschläge, wenn Ihr Code im Hauptthread ausgeführt wird.)

Robin Stewart
quelle
3

Um eine einfache Zeitverzögerung zu erstellen, können Sie Darwin importieren und dann die Verzögerung mit Sleep (Sekunden) ausführen. Dies dauert jedoch nur ganze Sekunden. Für genauere Messungen können Sie Darwin importieren und usleep (Millionstelsekunden) für sehr genaue Messungen verwenden. Um dies zu testen, schrieb ich:

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

Welcher druckt, wartet dann 1 Sekunde und druckt, wartet dann 0,4 Sekunden und druckt dann. Alle haben wie erwartet funktioniert.

Ethan Wrightson
quelle
-3

das ist das einfachste

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })
taha
quelle
2
Dies ist kein Teil von Foundation, sondern, wie ich annehme, im Cocoapod 'HandySwift' enthalten. Sie sollten dies in Ihrer Antwort klarstellen.
Jan Nash
Hat Swift etwas, das darauf wartet, dass etwas passiert oder nicht?
Saeed Rahmatolahi