等待asynchronous操作在Swift中完成

我不知道如何处理这种情况,因为我对iOS开发和Swift非常陌生。 我正在执行数据获取,如下所示:

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

我的loadShows()函数分析从一个网站加载到UIWebView的一堆数据。 问题是我有一个计时器,在loadShows函数中等待10秒左右。 这允许在我开始分析数据之前,页面中的JavaScript完全加载。 我的问题是完成处理程序在我的loadShows()之前完成。

我想要做的是添加一个“isCompletedParsingShows”布尔,并使completionHandler行等待完成,直到该布尔值为true。 处理这个问题的最好方法是什么?

你必须通过你的asynchronous函数处理程序稍后调用:

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

或(更干净的方式恕我直言)

添加一个中间的completionHandler

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

两种方法来解决这个问题,都使用Grand Central Dispatch (在Swift和Objective C中类似):

  1. 改变loadShows方法使其同步并使用与completionHandler相同的调度队列,然后将方法的整个主体包装在dispatch_async中 ; 这样方法调用马上就结束了,但是completionHandler将在loadShows之后被调用,如果在同步程序中

  2. 使用GCD信号量 – 就像你提到的BOOL,但用dispatch_semaphore_create创build; 你可以在completionHandler之前调用dispatch_semaphore_wait来等待信号被解锁(用dispatch_semaphore_signal解锁它)。 请记住将方法主体放在dispatch_async调用中,以便在等待loadShows完成时不会阻塞应用程序的其余部分。

细节

xCode 9.1,Swift 4

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

用法

 let asyncOperation = AsyncOperation(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString") asyncOperation.run { completeClosure in // Actions completeClosure() } 

完整的样品

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

结果

在这里输入图像说明

实现一个lockingvariables是直接的。 这对于做一些asynchronousnetworking加载的unit testing是最有帮助的。

 func waitingFunction() { //set a lock during your async function var locked = true RunSome.asyncFunction() { () -> Void in //after your sync function remove the lock locked = false }) //wait for the async method to complete before advancing while(locked){wait()} //move on from the lock doMoreStuff() } func wait() { NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 1)) }