Подтвердить что ты не робот

Подождите, пока асинхронная операция завершится в Swift

Я не уверен, как справиться с этой ситуацией, поскольку я очень новичок в разработке iOS и Swift. Я выполняю выборку данных так:

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

Функция loadShows() анализирует кучу данных, которые она получает с веб-сайта, загруженного в UIWebView. Проблема в том, что у меня есть таймер, который ждет 10 секунд или около того в функции loadShows. Это позволяет полностью загружать javascript на странице, прежде чем я начну синтаксический анализ данных. Моя проблема заключается в том, что обработчик завершения завершит работу до моего loadShows().

Что я хотел бы сделать, это добавить bool для "isCompletedParsingShows" и сделать завершающую строку completeHandler до завершения, пока это bool не будет истинным. Каков наилучший способ справиться с этим?

4b9b3361

Ответ 1

вам нужно передать свою асинхронную функцию обработчику для последующего вызова:

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

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

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

ИЛИ (более чистый способ IMHO)

добавить промежуточное завершениеHandler

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

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

Ответ 2

Два способа решить эту проблему: Grand Central Dispatch (что похоже на Swift и Objective C):

  • изменить метод loadShows, чтобы сделать его синхронным и использовать ту же очередь отправки, что и completeHandler, а затем обернуть весь кусок метода в dispatch_async; таким образом, вызов метода заканчивается сразу, но завершениеHandler будет вызываться после loadShows, если оно закончено, как в синхронной программе

  • используйте семафор GCD - точно так же, как упоминается BOOL, но созданный с помощью dispatch_semaphore_create; вы вызываете dispatch_semaphore_wait перед завершениемHandler, чтобы заставить его ждать разблокировки семафора (разблокируйте его с помощью dispatch_semaphore_signal); не забудьте разместить тело метода внутри вызова dispatch_async, чтобы он не блокировал остальную часть приложения, ожидая завершения loadShows.

Ответ 3

Подробнее

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

}

Результаты

введите описание изображения здесь

Ответ 4

Прямо реализовать блокирующую переменную. Это наиболее полезно для модульных тестов, которые выполняют некоторую загрузку асинхронной сети.

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