等待在Swift中完成异步操作

时间:2021-01-16 21:02:44

I am not sure how to handle this situation as I am very new to iOS development and Swift. I am performing data fetching like so:

我不知道如何处理这种情况,因为我是iOS开发和Swift的新手。我正在执行数据提取,如下所示:

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

My loadShows() function parses a bunch of data it gets from a website loaded into a UIWebView. The problem is that I have a timer that waits for 10 seconds or so in the loadShows function. This allows for the javascript in the page to fully load before I start parsing the data. My problem is that the completion handler completes before my loadShows() does.

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

What I would like to do is add a bool for "isCompletedParsingShows" and make the completionHandler line wait to complete until that bool is true. What is the best way to handle this?

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

4 个解决方案

#1


32  

you have to pass your async function the handler to call later on:

您必须将您的异步函数传递给稍后调用的处理程序:

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

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

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

OR (cleaner way IMHO)

add an intermediate completionHandler

添加一个中间completionHandler

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

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

#2


2  

two ways to solve this, both use Grand Central Dispatch (which is similar in Swift and Objective C):

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

  1. change loadShows method to make it synchronous and use the same dispatch queue as completionHandler, then wrap the entire body of the method in a dispatch_async ; this way the method call ends right away, but the completionHandler will be called after loadShows if finished, just like in a synchronous program

    更改loadShows方法以使其同步并使用与completionHandler相同的调度队列,然后将该方法的整个主体包装在dispatch_async中;这样方法调用立即结束,但是如果在loadShows完成后将调用completionHandler,就像在同步程序中一样

  2. use a GCD semaphore - just like the BOOL you mention, but created with dispatch_semaphore_create ; you call dispatch_semaphore_wait before completionHandler to make it wait for the semaphore to be unlocked (unlock it with dispatch_semaphore_signal ) ; remember to place your method body inside a dispatch_async call in order not to have it block the rest of the app while waiting for loadShows to complete.

    使用GCD信号量 - 就像你提到的BOOL一样,但是用dispatch_semaphore_create创建;你在completionHandler之前调用dispatch_semaphore_wait使它等待信号量被解锁(用dispatch_semaphore_signal解锁它);记得将方法体放在dispatch_async调用中,以免在等待loadShows完成时阻止其他应用程序。

#3


0  

Details

xCode 9.2, Swift 4

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

Full sample

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

}

Results

等待在Swift中完成异步操作

#4


-8  

It is straight forward to implement a locking variable. This is most helpful for unit tests that do some async network loading.

实现锁定变量是直截了当的。这对于执行某些异步网络加载的单元测试最有帮助。

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

#1


32  

you have to pass your async function the handler to call later on:

您必须将您的异步函数传递给稍后调用的处理程序:

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

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

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

OR (cleaner way IMHO)

add an intermediate completionHandler

添加一个中间completionHandler

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

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

#2


2  

two ways to solve this, both use Grand Central Dispatch (which is similar in Swift and Objective C):

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

  1. change loadShows method to make it synchronous and use the same dispatch queue as completionHandler, then wrap the entire body of the method in a dispatch_async ; this way the method call ends right away, but the completionHandler will be called after loadShows if finished, just like in a synchronous program

    更改loadShows方法以使其同步并使用与completionHandler相同的调度队列,然后将该方法的整个主体包装在dispatch_async中;这样方法调用立即结束,但是如果在loadShows完成后将调用completionHandler,就像在同步程序中一样

  2. use a GCD semaphore - just like the BOOL you mention, but created with dispatch_semaphore_create ; you call dispatch_semaphore_wait before completionHandler to make it wait for the semaphore to be unlocked (unlock it with dispatch_semaphore_signal ) ; remember to place your method body inside a dispatch_async call in order not to have it block the rest of the app while waiting for loadShows to complete.

    使用GCD信号量 - 就像你提到的BOOL一样,但是用dispatch_semaphore_create创建;你在completionHandler之前调用dispatch_semaphore_wait使它等待信号量被解锁(用dispatch_semaphore_signal解锁它);记得将方法体放在dispatch_async调用中,以免在等待loadShows完成时阻止其他应用程序。

#3


0  

Details

xCode 9.2, Swift 4

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

Full sample

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

}

Results

等待在Swift中完成异步操作

#4


-8  

It is straight forward to implement a locking variable. This is most helpful for unit tests that do some async network loading.

实现锁定变量是直截了当的。这对于执行某些异步网络加载的单元测试最有帮助。

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