Comment puis-je dispatch_sync, dispatch_async, dispatch_after, etc. dans Swift 3, Swift 4 et au-delà?

243

J'ai beaucoup de code dans les projets Swift 2.x (ou même 1.x) qui ressemble à ceci:

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

Ou des trucs comme ça pour retarder l'exécution:

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

Ou toute autre utilisation de l'API Grand Central Dispatch ...

Maintenant que j'ai ouvert mon projet dans Xcode 8 (beta) pour Swift 3, je reçois toutes sortes d'erreurs. Certains d'entre eux proposent de corriger mon code, mais tous les correctifs ne produisent pas de code de travail. Que dois-je faire à ce sujet?

rickster
la source

Réponses:

343

Depuis le début, Swift a fourni quelques fonctionnalités pour rendre ObjC et C plus Swifty, en ajoutant plus avec chaque version. Maintenant, dans Swift 3, la nouvelle fonctionnalité "importer en tant que membre" permet aux frameworks avec certains styles d'API C - où vous avez un type de données qui fonctionne un peu comme une classe, et un tas de fonctions globales pour travailler avec - agissent plus comme des API natives de Swift. Les types de données sont importés en tant que classes Swift, leurs fonctions globales associées sont importées en tant que méthodes et propriétés sur ces classes, et certaines choses connexes comme des ensembles de constantes peuvent devenir des sous-types, le cas échéant.

Dans Xcode 8 / Swift 3 beta, Apple a appliqué cette fonctionnalité (avec quelques autres) pour rendre le framework Dispatch beaucoup plus Swifty. (Et Core Graphics aussi.) Si vous avez suivi les efforts open source de Swift, ce n'est pas une nouveauté , mais c'est maintenant la première fois qu'il fait partie de Xcode.

La première étape pour déplacer un projet vers Swift 3 devrait être de l'ouvrir dans Xcode 8 et de choisir Édition> Convertir> En syntaxe Swift actuelle ... dans le menu. Cela s'appliquera (avec votre examen et votre approbation) à toutes les modifications nécessaires à la fois pour toutes les API renommées et autres modifications. (Souvent, une ligne de code est affectée par plusieurs de ces modifications à la fois, donc répondre individuellement aux correctifs d'erreur peut ne pas tout gérer correctement.)

Le résultat est que le modèle courant de rebond du travail à l'arrière-plan et à l'arrière ressemble maintenant à ceci:

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

Notez que nous utilisons à la .userInitiatedplace de l'une des anciennes DISPATCH_QUEUE_PRIORITYconstantes. Des spécificateurs de qualité de service (QoS) ont été introduits dans OS X 10.10 / iOS 8.0, fournissant un moyen plus clair pour le système de prioriser le travail et de déprécier les anciens spécificateurs de priorité. Consultez les documents d'Apple sur les travaux en arrière-plan et l'efficacité énergétique pour plus de détails.

Soit dit en passant, si vous conservez vos propres files d'attente pour organiser le travail, la façon d'en obtenir une ressemble maintenant à ceci (notez que DispatchQueueAttributesc'est un OptionSet, donc vous utilisez des littéraux de style collection pour combiner les options):

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

Vous dispatch_afterutilisez pour travailler plus tard? C'est aussi une méthode sur les files d'attente, et elle prend un DispatchTime, qui a des opérateurs pour différents types numériques afin que vous puissiez simplement ajouter des secondes entières ou fractionnaires:

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

Vous pouvez trouver votre chemin dans la nouvelle API Dispatch en ouvrant son interface dans Xcode 8 - utilisez Ouvrir rapidement pour trouver le module Dispatch, ou mettez un symbole (comme DispatchQueue) dans votre projet / terrain de jeu Swift et cliquez dessus avec le bouton de commande, puis faites le tour le module à partir de là. (Vous pouvez trouver l' API Swift Dispatch dans le nouveau site Web de référence d'API et la visionneuse de documents in-Xcode d'Apple, mais il semble que le contenu du document de la version C n'y soit pas encore entré.)

Consultez le Guide de migration pour plus de conseils.

rickster
la source
3
Comme pour Xcode 8 Beta 6, l'attribut .serial a disparu et le comportement par défaut - forums.developer.apple.com/message/159457#159457
hyouuu
6
Cela nécessite une mise à jour depuis XCode 8.1 .. l'étiquette des attributs a disparu et à sa place, nous pouvons utiliser 'DispatchQueue.global (qos: .background) .async'
Mike M
2
Magnifique réponse. Ça m'a vraiment aidé à comprendre.
Mohsin Khubaib Ahmed
J'ai dû utiliser à la qos:place deattributes:
Islam Q.
Cela ne devrait-il pas être myQueue.async {dans l' class Fooexemple?
vacawama
142

Dans Xcode 8 beta 4 ne fonctionne pas ...

Utilisation:

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

pour asynchroniser de deux manières:

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

DispatchQueue.main.async( execute: {
    print("Async2")
})
ingconti
la source
Donc, cela ne bloque pas l'interface utilisateur?
user25
72

Celui-ci est un bon exemple pour Swift 4environ async:

DispatchQueue.global(qos: .background).async {
    // Background Thread
    DispatchQueue.main.async {
        // Run UI Updates or call completion block
    }
}
S. Matsepura
la source
salut DispatchQueue.main.async {// Exécuter les mises à jour de l'interface utilisateur} s'exécute avant le thread d'arrière-plan
Uma Achanta
similaire avec les coroutines de Kotlin
user25
40

dans Xcode 8, utilisez:

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

Swift 5.2, 4 et versions ultérieures

Files d'attente principales et d'arrière-plan

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

Travailler avec des threads asynchrones et synchronisés !

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

Les threads asynchrones fonctionneront avec le thread principal.

Les threads de synchronisation bloqueront le thread principal lors de l'exécution.

Saranjith
la source
1
Et comment utiliseriez-vous les threads de synchronisation sans bloquer le thread principal (UI) ?? Je voudrais exécuter une rangée de choses en arrière-plan - mais ces choses doivent être exécutées l'une après l'autre de manière synchronisée. Pendant ce temps, l'interface utilisateur doit rester réactive ... Comment feriez-vous cela?
iKK
Utilisez NSOperationQueue. Quelle est votre tâche représentant une NSOperation. voir stackoverflow.com/a/19746890/5215474
Saranjith
12

Swift 4.1 et 5. Nous utilisons des files d'attente à de nombreux endroits dans notre code. J'ai donc créé la classe Threads avec toutes les files d'attente. Si vous ne souhaitez pas utiliser la classe Threads, vous pouvez copier le code de file d'attente souhaité à partir des méthodes de classe.

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

Exemple montrant l'utilisation de la file d'attente principale.

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