web-dev-qa-db-fra.com

Attendez que l'opération asynchrone se termine dans Swift

Je ne suis pas sûr de savoir comment gérer cette situation car je suis très novice dans le développement iOS et Swift. J'effectue l'extraction des données comme suit:

func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!)
{
    loadShows()
    completionHandler(UIBackgroundFetchResult.NewData)
    println("Background Fetch Complete")
}

Ma fonction loadShows () analyse un ensemble de données provenant d'un site Web chargé dans un UIWebView. Le problème est que j'ai une minuterie qui attend environ 10 secondes dans la fonction loadShows. Cela permet au javascript de la page de se charger complètement avant que je commence à analyser les données. Mon problème est que le gestionnaire d'achèvement se termine avant mon loadShows (). 

Ce que j'aimerais faire, c'est ajouter une valeur bool pour "isCompletedParsingShows" et faire en sorte que la ligne completionHandler attende jusqu'à ce que cette valeur bool soit vraie. Quelle est la meilleure façon de gérer cela?

23
PretzelJesus

vous devez transmettre votre fonction asynchrone au gestionnaire pour appeler plus tard: 

func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
    loadShows(completionHandler)
}

func loadShows(completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
    //....
    //DO IT
    //....

    completionHandler(UIBackgroundFetchResult.NewData)
    println("Background Fetch Complete")
}

OU (façon plus propre IMHO)

ajouter un AchèvementHandler intermédiaire

func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
    loadShows() {
        completionHandler(UIBackgroundFetchResult.NewData)
        println("Background Fetch Complete")
    }
}

func loadShows(completionHandler: (() -> Void)!) {
    //....
    //DO IT
    //....
    completionHandler()
}
32
Daij-Djan

deux façons de résoudre ce problème, les deux utilisent Grand Central Dispatch (qui est similaire dans Swift et Objective C): 

  1. changez la méthode loadShows pour la rendre synchrone et utilisez la même file d'attente de dispatch que completionHandler, puis enveloppez le corps entier de la méthode dans un dispatch_async; Ainsi, l'appel à la méthode se termine immédiatement, mais d'achèvement -Handler sera appelé après loadShows si elle est terminée, comme dans un programme synchrone.

  2. utilisez un sémaphore GCD - comme le BOOL que vous avez mentionné, mais créé avec dispatch_semaphore_create; vous appelez dispatch_semaphore_wait avant completionHandler pour le faire attendre que le sémaphore soit déverrouillé (déverrouillez-le avec dispatch_semaphore_signal); N'oubliez pas de placer votre corps de méthode dans un appel dispatch_async afin de ne pas le laisser bloquer le reste de l'application en attendant que loadShows soit terminé.

2
Alex

Détails

xCode 9.2, Swift 4

Solution

class AsyncOperation {

    private let semaphore: DispatchSemaphore
    private let dispatchQueue: DispatchQueue
    typealias CompleteClosure = ()->()

    init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) {
        semaphore = DispatchSemaphore(value: numberOfSimultaneousActions)
        dispatchQueue = DispatchQueue(label: dispatchQueueLabel)
    }

    func run(closure: @escaping (@escaping CompleteClosure)->()) {
        dispatchQueue.async {
            self.semaphore.wait()
            closure {
                self.semaphore.signal()
            }
        }
    }
}

Usage

let asyncOperation = AsyncOperation(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString")
asyncOperation.run { completeClosure in
    // sync/async action
    // ...


    // action complete        
    completeClosure()
}

Échantillon complet

import UIKit

class ViewController: UIViewController {

    let asyncOperation = AsyncOperation(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString")
    var counter = 1

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton(frame: CGRect(x: 50, y: 50, width: 100, height: 40))
        button.setTitle("Button", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button)

    }

    @objc func buttonTapped() {
        print("Button tapped at: \(Date())")
        asyncOperation.run { completeClosure in
            let counter = self.counter
            print("     - Action \(counter) strat at \(Date())")
            self.counter += 1

            DispatchQueue.global(qos: .background).async {
                sleep(1)
                print("     - Action \(counter) end at \(Date())")
                completeClosure()
            }
        }
    }

}

Résultats

 enter image description here

0
Vasily Bodnarchuk