¿Cómo crear un retraso en Swift?

262

Quiero pausar mi aplicación en cierto punto. En otras palabras, quiero que mi aplicación ejecute el código, pero luego, en cierto punto, haga una pausa de 4 segundos y luego continúe con el resto del código. ¿Cómo puedo hacer esto?

Estoy usando Swift

Schuey999
fuente

Respuestas:

271

En lugar de una suspensión, que bloqueará su programa si se llama desde el hilo de la interfaz de usuario, considere usar NSTimerun temporizador de envío.

Pero, si realmente necesita un retraso en el hilo actual:

do {
    sleep(4)
}

Esto usa la sleepfunción de UNIX.

nneonneo
fuente
44
el sueño funciona sin importar Darwin, no estoy seguro de si esto es un cambio
Dan Beaulieu
17
usleep () también funciona si necesita algo más preciso que una resolución de 1 segundo.
prewett
18
usleep () toma millonésimas de segundo, por lo que usleep (1000000) dormirá durante 1 segundo
Harris
40
Gracias por mantener corto el preámbulo de "no hagas esto".
Dan Rosenstark
2
Yo uso sleep () para simular procesos en segundo plano de larga duración.
William T. Mallard
345

dispatch_afterEn la mayoría de los casos, usar un bloque es mejor que usarlo, sleep(time)ya que el hilo en el que se realiza el sueño no puede realizar otro trabajo. cuando se utiliza dispatch_afterel hilo en el que se trabaja no se bloquea, por lo que puede hacer otro trabajo mientras tanto.
Si está trabajando en el hilo principal de su aplicación, el uso sleep(time)es malo para la experiencia del usuario de su aplicación ya que la interfaz de usuario no responde durante ese tiempo.

Despachar después de programar la ejecución de un bloque de código en lugar de congelar el hilo:

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
}
Pálido
fuente
1
¿Qué pasa con las acciones periódicas?
qed
1
Hay posibilidades de usar gcd para tareas periódicas que se describen en este artículo de la documentación del desarrollador de Apple, pero recomendaría usar un NSTimer para eso. Esta respuesta muestra cómo hacer exactamente eso rápidamente.
Palle
2
Código actualizado para Xcode 8.2.1. Anteriormente .now() + .seconds(4)da error:expression type is ambiguous without more context
richards
¿Cómo puedo suspender la función invocada desde la cola? @Palle
Mukul More
14
Ya no necesita agregarlo .now() + .seconds(5)es simplemente.now() + 5
cnzac
45

Comparación entre diferentes enfoques en swift 3.0

1. dormir

Este método no tiene una devolución de llamada. Poner códigos directamente después de esta línea para ejecutar en 4 segundos. Evitará que el usuario itere con elementos de la interfaz de usuario como el botón de prueba hasta que se acabe el tiempo. Aunque el botón se congela cuando se activa el sueño, otros elementos, como el indicador de actividad, siguen girando sin congelarse. No puede activar esta acción nuevamente durante el sueño.

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

ingrese la descripción de la imagen aquí

2. Despacho, realización y temporizador

Estos tres métodos funcionan de manera similar, todos se ejecutan en el subproceso de fondo con devoluciones de llamadas, solo con una sintaxis diferente y características ligeramente diferentes.

Dispatch se usa comúnmente para ejecutar algo en el hilo de fondo. Tiene la devolución de llamada como parte de la llamada de función

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

Realizar es en realidad un temporizador simplificado. Configura un temporizador con el retraso, y luego activa la función con el selector.

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

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

Y finalmente, el temporizador también proporciona la capacidad de repetir la devolución de llamada, lo que no es útil en este caso

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


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

Para todos estos tres métodos, cuando hace clic en el botón para activarlos, la IU no se congelará y podrá volver a hacer clic en él. Si vuelve a hacer clic en el botón, se configura otro temporizador y la devolución de llamada se activará dos veces.

ingrese la descripción de la imagen aquí

En conclusión

Ninguno de los cuatro métodos funciona lo suficientemente bien solo. sleepdesactivará la interacción del usuario, por lo que la pantalla se " congela " (en realidad no) y da como resultado una mala experiencia del usuario. Los otros tres métodos no congelarán la pantalla, pero puede activarlos varias veces, y la mayoría de las veces, desea esperar hasta que reciba la llamada antes de permitir que el usuario realice la llamada nuevamente.

Por lo tanto, un mejor diseño utilizará uno de los tres métodos asíncronos con bloqueo de pantalla. Cuando el usuario haga clic en el botón, cubra toda la pantalla con una vista translúcida con un indicador de actividad giratoria en la parte superior, que le indica al usuario que se está manejando el botón. Luego, elimine la vista y el indicador en la función de devolución de llamada, diciéndole al usuario que la acción se maneja correctamente, etc.

Colmillo
fuente
1
Tenga en cuenta que en Swift 4, con la inferencia objc desactivada, tendrá que agregar @objcdelante de la función de devolución de llamada para la perform(...)opción. Así:@objc func callback() {
leanne
32

Estoy de acuerdo con Palle en que usar dispatch_afteres una buena opción aquí. Pero probablemente no le gusten las llamadas GCD, ya que son bastante molestas de escribir . En su lugar, puede agregar este útil ayudante :

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

Ahora simplemente retrasa su código en un hilo de fondo como este:

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

Retrasar el código en el hilo principal es aún más simple:

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

Si prefiere un Framework que también tenga algunas características más útiles, entonces consulte HandySwift . Puede agregarlo a su proyecto a través de Cartago o Accio y luego usarlo exactamente como en los ejemplos anteriores:

import HandySwift    

delay(by: .seconds(1.5)) { 
    // delayed code
}
Jeehut
fuente
Esto parece muy útil. ¿Te importaría actualizarlo con una versión rápida de 3.0? Gracias
grahamcracker1234
Comencé a migrar HandySwift a Swift 3 y planeo lanzar una nueva versión la próxima semana. Entonces también actualizaré el script anterior. Manténganse al tanto.
Jeehut
La migración de HandySwift a Swift 3 ahora está completa y se lanzó en la versión 1.3.0. ¡También acabo de actualizar el código anterior para que funcione con Swift 3! :)
Jeehut
1
¿Por qué multiplicas por NSEC_PER_SEC y luego vuelves a dividir por ellas? ¿No es eso redundante? (por ejemplo, Doble (Int64 (segundos * Doble (NSEC_PER_SEC))) / Doble (NSEC_PER_SEC)) ¿No podría simplemente escribir 'DispatchTime.now () + Doble (segundos)?
Mark A. Donohoe
1
Gracias @MarqueIV, tienes razón, por supuesto. Acabo de actualizar el código. Fue simplemente el resultado de la conversión automática de Xcode 8 y la conversión de tipo era necesaria antes, ya que DispatchTimeno estaba disponible en Swift 2. Era let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC)))antes.
Jeehut
24

También puedes hacer esto con Swift 3.

Realice la función después del retraso así.

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")
        }
Cirilo
fuente
23

En Swift 4.2 y Xcode 10.1

Tienes 4 formas en total para retrasar. Fuera de estas opciones 1 es preferible llamar o ejecutar una función después de un tiempo. El sueño () es menos caso en uso.

Opción 1.

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

}

Opcion 2.

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

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

Opcion 3.

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

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

}

Opcion 4.

sleep(5)

Si desea llamar a una función después de un tiempo para ejecutar algo, no use el modo de suspensión .

iOS
fuente
19

NSTimer

La respuesta de @nneonneo sugirió usar, NSTimerpero no mostró cómo hacerlo. Esta es la sintaxis básica:

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

Aquí hay un proyecto muy simple para mostrar cómo podría usarse. Cuando se presiona un botón, se inicia un temporizador que llamará a una función después de un retraso de medio segundo.

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

Usar dispatch_time(como en la respuesta de Palle ) es otra opción válida. Sin embargo, es difícil de cancelar . Con NSTimer, para cancelar un evento retrasado antes de que ocurra, todo lo que necesita hacer es llamar

timer.invalidate()

El uso sleepno se recomienda, sobre todo en el hilo principal, ya que detiene todo el trabajo que se realiza en el hilo.

Vea aquí para mi respuesta más completa.

Suragch
fuente
7

Pruebe la siguiente implementación en Swift 3.0

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

Uso

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

Puede crear una extensión para usar la función de retraso fácilmente (Sintaxis: 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)
    }
}

Cómo usar en UIViewController

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

Si necesita establecer un retraso de menos de un segundo, no es necesario configurar el parámetro .seconds. Espero que esto sea útil para alguien.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        // your code hear
})
Booharin
fuente
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
    }
}
eonista
fuente
44
Hacer DispatchQueue.main.asyncAfter(deadline: .now() + 4) {/*Do stuff*/}es probablemente más correcto ✌️
eonist
5

Si su código ya se está ejecutando en un hilo de fondo, pause el hilo usando este método en Foundation :Thread.sleep(forTimeInterval:)

Por ejemplo:

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

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

(Consulte otras respuestas para obtener sugerencias cuando su código se ejecuta en el hilo principal).

Robin Stewart
fuente
3

Para crear un retraso de tiempo simple, puede importar Darwin y luego usar la suspensión (segundos) para hacer el retraso. Sin embargo, eso solo lleva segundos enteros, por lo que para mediciones más precisas puede importar Darwin y usar usleep (millonésimas de segundo) para una medición muy precisa. Para probar esto, escribí:

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

Que imprime, luego espera 1 segundo e imprime, luego espera 0,4 segundos y luego imprime. Todo funcionó como se esperaba.

Ethan Wrightson
fuente
-3

este es el más simple

    delay(0.3, closure: {
        // put her any code you want to fire it with delay
        button.removeFromSuperview()   
    })
taha
fuente
2
Esto no es parte de Foundation, está, como supongo, incluido en el Cocoapod 'HandySwift'. Deberías aclarar eso en tu respuesta.
Jan Nash el
¿Swift tiene algo que espera que algo suceda o no?
Saeed Rahmatolahi