dispatch_after - GCD en Swift?


559

Revisé el iBook de Apple y no pude encontrar ninguna definición:

¿Alguien puede explicar la estructura de dispatch_after?

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

1
Apple no publicó este libro en 2018. El último archivo que pude encontrar es de diciembre de 2017 . Los viejos enlaces al iBook ahora simplemente redirigen a developer.apple.com/documentation/swift .
Cœur

Respuestas:


742

Una idea más clara de la estructura:

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

dispatch_time_tes un UInt64. En dispatch_queue_trealidad, el tipo está alias a an NSObject, pero solo debe usar sus métodos GCD familiares para obtener colas. El bloque es un cierre Swift. Específicamente, dispatch_block_tse define como () -> Void, que es equivalente a () -> ().

Ejemplo de uso:

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

EDITAR:

Recomiendo usar la delayfunción realmente agradable de @ matt .

EDITAR 2:

En Swift 3, habrá nuevos contenedores para GCD. Ver aquí: https://github.com/apple/swift-evolution/blob/master/proposals/0088-libdispatch-for-swift3.md

El ejemplo original se escribiría de la siguiente manera en Swift 3:

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

Tenga en cuenta que puede escribir la deadlineTimedeclaración como DispatchTime.now() + 1.0y obtener el mismo resultado porque el +operador se anula de la siguiente manera (de manera similar para -):

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

Esto significa que si no usa el DispatchTimeInterval enumy simplemente escribe un número, se supone que está usando segundos.


17
Consejo: Debido a que el bloque es el parámetro final de la función, puede usar la sintaxis de "cierre final" de Swift para una legibilidad adicional:dispatch_after(1, dispatch_get_main_queue()) { println("test") }
Bill

8
Creo que usando el número 1de dispatch_after(1, ...puede causar mucha confusión aquí. La gente pensará que es una cantidad de segundos, cuando en realidad es nano-segundo . Sugiero ver la respuesta de @brindy sobre cómo crear este número correctamente.
Hlung

3
Cambie 1a dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))porque conduce a la confusión. La gente podría pensar que no es necesario crear un dispatch_time_t en Swift
OemerA

44
La versión Swift 3 no parece estar funcionando. Se queja de que Binary operator '+' cannot be applied to operands of type DispatchTime and '_'en la línealet delayTime = DispatchTime.now() + .seconds(1.0)
Andy Ibáñez

99
Reescribirlo como DispatchTime.now() + 1.0parece ser la única forma de hacerlo funcionar (no es necesario .seconds)
Andy Ibáñez

1093

Utilizo dispatch_aftertantas veces que escribí una función de utilidad de nivel superior para simplificar la sintaxis:

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

Y ahora puedes hablar así:

delay(0.4) {
    // do stuff
}

Wow, un idioma donde puedes mejorar el idioma. ¿Que podría ser mejor?


Actualización para Swift 3, Xcode 8 Seed 6

Parece que no vale la pena molestarse, ahora que han mejorado la sintaxis de llamadas:

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

2
Solo necesitaba un atajo para el cálculo del retraso, terminé con:func delayInSec(delay: Double) -> dispatch_time_t { return dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))) }
Aviel Gross

44
@ agf119105 Si solo tiene una línea de código en el cierre, agregue otra línea de código (por ejemplo return).
mate

2
@GastonM Irrelevante. Pasar una función por sí mismo no tiene problemas de administración de memoria.
mate

77
"Un idioma donde puedes mejorar el idioma". No entiendo cómo definir una función global está mejorando el lenguaje, o por qué esto no es factible incluso en C. Tal vez si sobrecarga un operador;)1.0 ~~ { code...}
Yerk

8
No cuestiona la exactitud de su respuesta, pero ¿no es "Yo uso dispatch_after con tanta frecuencia" un olor a código que sería mejor combatir al no proporcionar una función de conveniencia?
Nikolai Ruhe

128

Swift 3+

Esto es súper fácil y elegante en Swift 3+:

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

Respuesta anterior:

Para ampliar la respuesta de Cezary, que se ejecutará después de 1 nanosegundo, tuve que hacer lo siguiente para ejecutar después de 4 segundos y medio.

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)

Editar: descubrí que mi código original estaba ligeramente equivocado. La escritura implícita provoca un error de compilación si no convierte NSEC_PER_SEC en un Double.

Si alguien puede sugerir una solución más óptima, me gustaría escucharla.


Me sale un error del compilador para una API obsoleta con dispatch_get_current_queue(). Yo solía en su dispatch_get_main_queue()lugar.
David L

@DavidL - gracias, dispatch_get_main_queue()definitivamente es lo que deberías estar usando. Actualizará.
brindy


@GAlexander funciona para mí. ¿Estás permitiendo que el patio se ejecute indefinidamente?
brindy

uhm, bueno no, dejé correr un par de horas y todavía no se imprimió nada Esto es lo que usé. "import Dispatch import Darwin import CoreGraphics 'DispatchQueue.main.asyncAfter (fecha límite: .now () + 4.5) {print (" llegó aquí ")}"
μολὼν.λαβέ

83

la sintaxis de matt es muy buena y si necesita invalidar el bloque, puede usar esto:

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

Use de la siguiente manera

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

créditos

El enlace de arriba parece estar inactivo. Código Objc original de Github


1
La única característica de rendimiento que tiene performSelector: afterDelay es la capacidad de cancelarla. Solo esta solución cubre el problema. Gracias
HotJard

@HotJard Tenga en cuenta que performSelector:afterDelay:ahora está disponible en Swift 2, por lo que puede cancelarlo.
mate

@matt pero está disponible solo para NSObject, ¿no?
HotJard

@HotJard Claro, pero eso es mejor que no tenerlo en absoluto. No veo ningún problema allí. Sin embargo, al igual que con esta respuesta, ya había compensado su pérdida escribiendo un temporizador cancelable basado en GCD (usando un dispatch_source_t, porque eso es algo que puede cancelar).
mate

2
Muchas gracias, he estado usando esto hasta Swift 2.3. El compilador Swift 3.0 se queja ahora, ¡sería genial si actualizaras tu respuesta!
tópico

27

La solución más simple en Swift 3.0 y Swift 4.0 y Swift 5.0

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

Uso

delayWithSeconds(1) {
   //Do something
}

22

Apple tiene un fragmento de dispatch_after para 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#>
});

Aquí está el mismo fragmento portado a Swift 3:

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

14

Otra forma es extender Double así:

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

Entonces puedes usarlo así:

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

Me gusta la función de retraso de Matt, pero solo por preferencia, prefiero limitar los pases de cierre.


8

En Swift 3.0

Despacho de colas

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

Despacho después de 5 segundos

    DispatchQueue.main.after(when: DispatchTime.now() + 5) {
        print("Dispatch after 5 sec")
    }

4

Versión Swift 3.0

Después de la función de cierre, ejecute alguna tarea después del retraso en el hilo principal.

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

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

Llame a esta función como:

performAfterDelay(delay: 4.0) {
  print("test")
}

4

1) Agregue este método como parte de la extensión 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)
    }
}

Llame a este método en 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
})

// Forma compacta

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue()) {
    //Code here
 }
}

3

Aunque no es la pregunta original del OP, ciertas NSTimerpreguntas relacionadas se han marcado como duplicados de esta pregunta, por lo que vale la pena incluir una NSTimerrespuesta aquí.

NSTimer vs dispatch_after

  • NSTimeres más alto nivel mientras que dispatch_afteres más bajo nivel.
  • NSTimerEs más fácil de cancelar. Cancelar dispatch_afterrequiere escribir más código .

Retrasar una tarea con NSTimer

Crea una NSTimerinstancia.

var timer = NSTimer()

Inicie el temporizador con el retraso que necesita.

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

Agregue una función que se llamará después del retraso (usando cualquier nombre que haya utilizado para el selectorparámetro anterior).

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

Notas

  • Si necesita cancelar la acción antes de que ocurra, simplemente llame timer.invalidate().
  • Para una acción repetida uso repeats: true.
  • Si tiene un evento único sin necesidad de cancelar, no es necesario crear la timervariable de instancia. Lo siguiente será suficiente:

    NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 
  • Vea mi respuesta más completa aquí .


3

Para múltiples funciones use esto. Es muy útil usar animaciones o el cargador de actividades para funciones estáticas o cualquier actualización de la interfaz de usuario.

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

Por ejemplo: use una animación antes de que se recargue una tableView. O cualquier otra actualización de la interfaz de usuario después de la animación.

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

2

Esto funcionó para mí.

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

C objetivo:

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

2

Swift 3 y 4:

Puede crear una extensión en DispatchQueue y agregar un retraso de función que utilice la función DispatchQueue asyncAfter internamente

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

utilizar:

DispatchQueue.delay(.seconds(1)) {
    print("This is after delay")
}

1

Otro ayudante para retrasar su código que es 100% rápido en el uso y opcionalmente permite elegir un hilo diferente para ejecutar su código retrasado desde:

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 el hilo principal de esta manera:

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

Si desea retrasar su código a un hilo diferente :

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background 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 Carthage y luego usarlo exactamente como en los ejemplos anteriores, por ejemplo:

import HandySwift    

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

1

Siempre prefiero usar la extensión en lugar de las funciones gratuitas.

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

}

Use de la siguiente manera.

DispatchQueue.performAction(after: 0.3) {
  // Code Here
}

1

Retrasar la llamada GCD usando asyncAfter en Swift

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

Podemos retrasar como ** microsegundos , milisegundos , nanosegundos

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

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

1

En Swift 4

Usa este fragmento:

    let delayInSec = 1.0
    DispatchQueue.main.asyncAfter(deadline: .now() + delayInSec) {
       // code here
       print("It works")
    }

Esto ya está en otras respuestas (Brindy's, por ejemplo, o Rahul) ... misma sintaxis ...
Eric Aya

1
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // ...
});

La dispatch_after(_:_:_:)función toma tres parámetros:

un retraso
una cola de despacho
un bloque o cierre

La dispatch_after(_:_:_:)función invoca el bloque o cierre en la cola de despacho que se pasa a la función después de un retraso dado. Tenga en cuenta que el retraso se crea utilizando la dispatch_time(_:_:)función. Recuerde esto porque también usamos esta función en Swift.

Recomiendo pasar por el tutorial Raywenderlich Dispatch tutorial


1

En Swift 5, use en lo siguiente:

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

0

use este código para realizar alguna tarea relacionada con la interfaz de usuario después de 2.0 segundos.

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

Versión Swift 3.0

Después de la función de cierre, ejecute alguna tarea después del retraso en el hilo principal.

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

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

Llame a esta función como:

performAfterDelay(delay: 4.0) {
  print("test")
}

1
Esto es casi idéntico a las respuestas anteriores
Daniel Galasko

Parece que esta respuesta se llevó a cabo a principios de 2016, y es mayor de al menos otros 6 respuestas ..
eharo2

0

Ahora más que el azúcar sintáctico para despachos asincrónicos en Grand Central Dispatch (GCD) en Swift.

agregar Podfile

pod 'AsyncSwift'

Entonces, puedes usarlo así.

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

Apple nos ha dado todo lo necesario para usar GCD en pocas líneas. ¿Por qué molestarse con las vainas, el espacio de trabajo, etc.? Simplemente lea documentos sobre @escaping y captura. es suficiente.
ingconti

0

Swift 4 tiene una forma bastante corta de hacer esto:

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

0

Aquí está la versión síncrona de asyncAfter en 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()

Junto con uno asíncrono:

let deadline = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadline) {
    dispatchPrecondition(condition: .onQueue(DispatchQueue.global()))
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.