¿Cómo despacho_sync, dispatch_async, dispatch_after, etc. en Swift 3, Swift 4 y más allá?

243

Tengo un montón de código en proyectos Swift 2.x (o incluso 1.x) que se ve así:

// Move to a background thread to do some long running work
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    let image = self.loadOrGenerateAnImage()
    // Bounce back to the main thread to update the UI
    dispatch_async(dispatch_get_main_queue()) {
        self.imageView.image = image
    }
}

O cosas como esta para retrasar la ejecución:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
    print("test")
}

O cualquiera de todo tipo de otros usos de Grand Central Dispatch API ...

Ahora que he abierto mi proyecto en Xcode 8 (beta) para Swift 3, obtengo todo tipo de errores. Algunos de ellos ofrecen arreglar mi código, pero no todas las correcciones producen código de trabajo. ¿Qué hago al respecto?

rickster
fuente

Respuestas:

343

Desde el principio, Swift ha proporcionado algunas facilidades para hacer que ObjC y C sean más Swifty, agregando más con cada versión. Ahora, en Swift 3, la nueva característica "importar como miembro" permite marcos con ciertos estilos de API C, donde tiene un tipo de datos que funciona como una clase y un montón de funciones globales para trabajar con él. actuar más como API nativas de Swift. Los tipos de datos se importan como clases Swift, sus funciones globales relacionadas se importan como métodos y propiedades en esas clases, y algunas cosas relacionadas, como conjuntos de constantes, pueden convertirse en subtipos cuando corresponda.

En Xcode 8 / Swift 3 beta, Apple ha aplicado esta función (junto con algunas otras) para hacer que el marco de Dispatch sea mucho más rápido. (Y Core Graphics , también.) Si ha estado siguiendo los esfuerzos de código abierto de Swift, esto no es noticia , pero ahora es la primera vez que forma parte de Xcode.

El primer paso para mover cualquier proyecto a Swift 3 debe ser abrirlo en Xcode 8 y elegir Edición> Convertir> A sintaxis actual de Swift ... en el menú. Esto aplicará (con su revisión y aprobación) todos los cambios a la vez necesarios para todas las API renombradas y otros cambios. (A menudo, una línea de código se ve afectada por más de uno de estos cambios a la vez, por lo que responder a la corrección de errores, es posible que no pueda manejar todo correctamente).

El resultado es que el patrón común para rebotar el trabajo en segundo plano y viceversa ahora se ve así:

// Move to a background thread to do some long running work
DispatchQueue.global(qos: .userInitiated).async {
    let image = self.loadOrGenerateAnImage()
    // Bounce back to the main thread to update the UI
    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

Tenga en cuenta que estamos usando en .userInitiatedlugar de una de las DISPATCH_QUEUE_PRIORITYconstantes antiguas . Los especificadores de calidad de servicio (QoS) se introdujeron en OS X 10.10 / iOS 8.0, proporcionando una forma más clara para que el sistema priorice el trabajo y desaproveche los antiguos especificadores de prioridad. Consulte los documentos de Apple sobre trabajo en segundo plano y eficiencia energética para más detalles.

Por cierto, si mantiene sus propias colas para organizar el trabajo, la forma de obtener uno ahora se ve así ( DispatchQueueAttributestenga en cuenta que es una OptionSet, así que usa literales de estilo de colección para combinar opciones):

class Foo { 
    let queue = DispatchQueue(label: "com.example.my-serial-queue",
                           attributes: [.serial, .qosUtility])
    func doStuff() {
        queue.async {
            print("Hello World")
        }
    }
}

¿Utilizando dispatch_afterpara hacer el trabajo más tarde? Ese también es un método en colas, y requiere un DispatchTime, que tiene operadores para varios tipos numéricos, por lo que puede agregar segundos enteros o fraccionarios:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // in half a second...
    print("Are we there yet?")
}

Puede orientarse en la nueva API de Despacho abriendo su interfaz en Xcode 8: use Abrir rápidamente para encontrar el módulo de Despacho, o coloque un símbolo (como DispatchQueue) en su proyecto / patio de juegos Swift y haga clic con el botón de comando, luego pase alrededor. El módulo desde allí. (Puede encontrar la API Swift Dispatch en el nuevo y sofisticado sitio web de referencia de API de Apple y en el visor de documentos en Xcode, pero parece que el contenido de documentos de la versión C aún no se ha movido).

Consulte la Guía de migración para obtener más consejos.

rickster
fuente
3
En cuanto a Xcode 8 Beta 6, el atributo .serial desapareció y el comportamiento predeterminado - forums.developer.apple.com/message/159457#159457
hyouuu
66
Esto necesita una actualización desde XCode 8.1 ... la etiqueta de atributos ha desaparecido y en su lugar podemos usar 'DispatchQueue.global (qos: .background) .async'
Mike M
2
Maravillosa respuesta. Realmente me ayudó a entenderlo.
Mohsin Khubaib Ahmed
Tuve que usar en qos:lugar deattributes:
Islam Q.
¿No debería ser eso myQueue.async {en el class Fooejemplo?
vacawama
142

En Xcode 8 beta 4 no funciona ...

Utilizar:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    print("Are we there yet?")
}

para asíncrono de dos maneras:

DispatchQueue.main.async {
    print("Async1")
}

DispatchQueue.main.async( execute: {
    print("Async2")
})
ingconti
fuente
¿Entonces no bloquea la interfaz de usuario?
user25
72

Éste es un buen ejemplo de Swift 4aproximadamente async:

DispatchQueue.global(qos: .background).async {
    // Background Thread
    DispatchQueue.main.async {
        // Run UI Updates or call completion block
    }
}
S. Matsepura
fuente
hola DispatchQueue.main.async {// Ejecutar actualizaciones de la interfaz de usuario} se está ejecutando antes del hilo de fondo
Uma Achanta
similar con Kotlin de corrutinas
USER25
40

en Xcode 8 use:

DispatchQueue.global(qos: .userInitiated).async { }
Marco
fuente
26

Swift 5.2, 4 y posterior

Colas principal y de fondo

let main = DispatchQueue.main
let background = DispatchQueue.global()
let helper = DispatchQueue(label: "another_thread") 

¡Trabajando con hilos asíncronos y sincronizados !

 background.async { //async tasks here } 
 background.sync { //sync tasks here } 

Los hilos asíncronos funcionarán junto con el hilo principal.

Los hilos de sincronización bloquearán el hilo principal mientras se ejecutan.

Saranjith
fuente
1
¿Y cómo usarías los hilos de sincronización sin bloquear el hilo principal (UI)? Me gustaría ejecutar una fila de cosas en segundo plano, pero estas cosas deben ejecutarse una tras otra de forma sincronizada. Durante este tiempo, la interfaz de usuario debe permanecer receptiva ... ¿Cómo haría eso?
iKK
Use NSOperationQueue. Que cada una de tus tareas representa una operación NSO. consulte stackoverflow.com/a/19746890/5215474
Saranjith
12

Swift 4.1 y 5. Utilizamos colas en muchos lugares de nuestro código. Entonces, creé la clase Threads con todas las colas. Si no desea utilizar la clase Threads, puede copiar el código de cola deseado de los métodos de clase.

class Threads {

  static let concurrentQueue = DispatchQueue(label: "AppNameConcurrentQueue", attributes: .concurrent)
  static let serialQueue = DispatchQueue(label: "AppNameSerialQueue")

  // Main Queue
  class func performTaskInMainQueue(task: @escaping ()->()) {
    DispatchQueue.main.async {
      task()
    }
  }

  // Background Queue
  class func performTaskInBackground(task:@escaping () throws -> ()) {
    DispatchQueue.global(qos: .background).async {
      do {
        try task()
      } catch let error as NSError {
        print("error in background thread:\(error.localizedDescription)")
      }
    }
  }

  // Concurrent Queue
  class func perfromTaskInConcurrentQueue(task:@escaping () throws -> ()) {
    concurrentQueue.async {
      do {
        try task()
      } catch let error as NSError {
        print("error in Concurrent Queue:\(error.localizedDescription)")
      }
    }
  }

  // Serial Queue
  class func perfromTaskInSerialQueue(task:@escaping () throws -> ()) {
    serialQueue.async {
      do {
        try task()
      } catch let error as NSError {
        print("error in Serial Queue:\(error.localizedDescription)")
      }
    }
  }

  // Perform task afterDelay
  class func performTaskAfterDealy(_ timeInteval: TimeInterval, _ task:@escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: (.now() + timeInteval)) {
      task()
    }
  }
}

Ejemplo que muestra el uso de la cola principal.

override func viewDidLoad() {
    super.viewDidLoad()
     Threads.performTaskInMainQueue {
        //Update UI
    }
}
Gurjinder Singh
fuente