Come creare un ritardo in Swift?


Risposte:


271

Invece di uno sleep, che bloccherà il tuo programma se chiamato dal thread dell'interfaccia utente, considera l'utilizzo NSTimero un timer di invio.

Ma, se hai davvero bisogno di un ritardo nel thread corrente:

do {
    sleep(4)
}

Questo utilizza la sleepfunzione di UNIX.


4
sleep funziona senza importare darwin, non sono sicuro che si tratti di un cambiamento
Dan Beaulieu,

17
usleep () funziona anche se hai bisogno di qualcosa di più preciso della risoluzione di 1 secondo.
prewett

18
usleep () richiede milionesimi di secondo, quindi usleep (1000000) dormirà per 1 secondo
Harris,

40
Grazie per aver abbreviato il preambolo "Non farlo".
Dan Rosenstark,

2
Uso sleep () per simulare processi in background di lunga durata.
William T. Mallard,

345

L'uso di un dispatch_afterblocco è nella maggior parte dei casi meglio dell'uso sleep(time)poiché il thread su cui viene eseguita la sospensione viene bloccato dall'altro lavoro. quando si utilizza dispatch_afteril thread su cui si lavora non viene bloccato, quindi può fare altri lavori nel frattempo.
Se stai lavorando sul thread principale della tua applicazione, l'utilizzo sleep(time)è negativo per l'esperienza utente della tua app poiché l'interfaccia utente non risponde in quel periodo.

Invia dopo pianifica l'esecuzione di un blocco di codice invece di bloccare il 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
}

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
}

1
Che dire delle azioni periodiche?
qed

1
ci sono possibilità di usare gcd per attività periodiche che sono descritte in questo articolo dalla documentazione per gli sviluppatori di apple, ma per questo consiglio di usare un NSTimer. questa risposta mostra come farlo esattamente in breve tempo.
Palle

2
Codice aggiornato per Xcode 8.2.1. Precedentemente .now() + .seconds(4)restituisce un errore:expression type is ambiguous without more context
richards,

Come posso sospendere la funzione invocata dalla coda? @Palle
Mukul Altro

14
Non è più necessario aggiungerlo .now() + .seconds(5)è semplicemente.now() + 5
cnzac

45

Confronto tra diversi approcci in swift 3.0

1. Dormire

Questo metodo non ha una richiamata. Inserisci i codici direttamente dopo questa riga per eseguirli in 4 secondi. Impedirà all'utente di scorrere gli elementi dell'interfaccia utente come il pulsante di test fino a quando non è trascorso il tempo. Sebbene il pulsante sia un po 'congelato quando entra in funzione il sonno, altri elementi come l'indicatore di attività continuano a girare senza congelamento. Non è possibile attivare nuovamente questa azione durante il sonno.

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

inserisci qui la descrizione dell'immagine

2. Spedizione, esecuzione e timer

Questi tre metodi funzionano in modo simile, sono tutti in esecuzione sul thread in background con callback, solo con sintassi diversa e caratteristiche leggermente diverse.

La spedizione è comunemente usata per eseguire qualcosa sul thread in background. Ha il callback come parte della chiamata di funzione

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

Perform è in realtà un timer semplificato. Imposta un timer con il ritardo, quindi attiva la funzione tramite selettore.

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

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

E infine, il timer offre anche la possibilità di ripetere la richiamata, il che non è utile in questo caso

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


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

Per tutti e tre questi metodi, quando si fa clic sul pulsante per attivarli, l'interfaccia utente non si blocca e si può fare nuovamente clic su di esso. Se si fa di nuovo clic sul pulsante, viene impostato un altro timer e la richiamata verrà attivata due volte.

inserisci qui la descrizione dell'immagine

In conclusione

Nessuno dei quattro metodi funziona abbastanza bene da solo. sleepdisabiliterà l'interazione dell'utente, quindi lo schermo "si blocca " (non effettivamente) e si traduce in un'esperienza utente negativa. Gli altri tre metodi non bloccano lo schermo, ma è possibile attivarli più volte e, la maggior parte delle volte, si desidera attendere fino a quando non si riceve nuovamente la chiamata prima di consentire all'utente di effettuare nuovamente la chiamata.

Quindi un design migliore utilizzerà uno dei tre metodi asincroni con blocco dello schermo. Quando l'utente fa clic sul pulsante, copre l'intero schermo con una vista traslucida con un indicatore di attività rotante in alto, che indica all'utente che il clic del pulsante viene gestito. Quindi rimuovere la vista e l'indicatore nella funzione di richiamata, dicendo all'utente che l'azione è gestita correttamente, ecc.


1
Nota che in Swift 4, con l'inferenza objc disattivata, dovrai aggiungere @objcdavanti alla funzione di richiamata per l' perform(...)opzione. In questo modo:@objc func callback() {
leanne,

32

Sono d'accordo con Palle che l'utilizzo dispatch_afterè una buona scelta qui. Ma probabilmente non ti piacciono le chiamate GCD in quanto sono abbastanza fastidiose da scrivere . Invece puoi aggiungere questo utile aiuto :

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 semplicemente ritardi il codice su un thread in background come questo:

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

Ritardare il codice sul thread principale è ancora più semplice:

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

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

import HandySwift    

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

Sembra molto utile. Ti dispiace aggiornarlo con una versione 3.0 rapida. Grazie
grahamcracker1234

Ho iniziato a migrare HandySwift su Swift 3 e ho intenzione di rilasciare una nuova versione la prossima settimana. Quindi aggiornerò anche lo script sopra. Rimanete sintonizzati.
Jeehut,

La migrazione di HandySwift a Swift 3 è ora completa e rilasciata nella versione 1.3.0. Ho anche appena aggiornato il codice sopra per funzionare con Swift 3! :)
Jeehut,

1
Perché stai moltiplicando per NSEC_PER_SEC per poi dividerlo di nuovo? Non è ridondante? (ad esempio Double (Int64 (secondi * Double (NSEC_PER_SEC))) / Double (NSEC_PER_SEC)) Non potresti semplicemente scrivere 'DispatchTime.now () + Double (secondi)?
Mark A. Donohoe,

1
Grazie @MarqueIV, hai ragione ovviamente. Ho appena aggiornato il codice. Era semplicemente il risultato della conversione automatica da Xcode 8 e la conversione del tipo era necessaria prima perché DispatchTimenon era disponibile in Swift 2. Era let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))prima.
Jeehut,

24

Puoi farlo anche con Swift 3.

Eseguire la funzione dopo il ritardo in questo modo.

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

In Swift 4.2 e Xcode 10.1

Hai 4 modi per ritardare. Di queste opzioni 1 è preferibile chiamare o eseguire una funzione dopo qualche tempo. Il sleep () è il caso minimo in uso.

Opzione 1.

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

}

Opzione 2.

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

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

Opzione 3

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

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

}

Opzione 4

sleep(5)

Se si desidera chiamare una funzione dopo qualche tempo per eseguire qualcosa, non utilizzare la modalità sospensione .


19

NSTimer

La risposta di @nneonneo ha suggerito di usare NSTimerma non ha mostrato come farlo. Questa è la sintassi di base:

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

Ecco un progetto molto semplice per mostrare come potrebbe essere usato. Quando viene premuto un pulsante, avvia un timer che chiamerà una funzione dopo un ritardo di mezzo secondo.

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'uso dispatch_time(come nella risposta di Palle ) è un'altra opzione valida. Tuttavia, è difficile da annullare . Con NSTimer, per annullare un evento ritardato prima che accada, tutto ciò che devi fare è chiamare

timer.invalidate()

L'uso sleepnon è raccomandato, specialmente sul thread principale, poiché interrompe tutto il lavoro svolto sul thread.

Vedi qui per la mia risposta più completa.


7

Prova la seguente implementazione in Swift 3.0

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

uso

delayWithSeconds(1) {
   //Do something
}

7

È possibile creare l'estensione per utilizzare facilmente la funzione di ritardo (Sintassi: 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)
    }
}

Come utilizzare in UIViewController

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

6

Se è necessario impostare un ritardo inferiore a un secondo, non è necessario impostare il parametro .seconds. Spero che questo sia utile a qualcuno.

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
Fare DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}è probabilmente più corretto ✌️
eonista il

5

Se il codice è già in esecuzione in un thread in background, sospendi il thread utilizzando questo metodo in Foundation :Thread.sleep(forTimeInterval:)

Per esempio:

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

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

(Vedi altre risposte per suggerimenti quando il tuo codice è in esecuzione sul thread principale.)


3

Per creare un semplice ritardo, è possibile importare Darwin e quindi utilizzare la sospensione (secondi) per eseguire il ritardo. Ciò richiede solo interi secondi, quindi per misure più precise puoi importare Darwin e usare usleep (milionesimi di secondo) per misure molto precise. Per provare questo, ho scritto:

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

Quale stampa, quindi attende 1 secondo e stampa, quindi attende 0,4 secondi quindi stampa. Tutto ha funzionato come previsto.


-3

questo è il più semplice

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

2
Questo non fa parte della Fondazione, è, come presumo, incluso nel Cocoapod "HandySwift". Dovresti chiarirlo nella tua risposta.
Jan Nash,

Swift ha qualcosa che aspetta che accada qualcosa o no?
Saeed Rahmatolahi,
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.