Подождите, пока асинхронная операция завершится в 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 не будет истинным. Каков наилучший способ справиться с этим?
Ответы
Ответ 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))
}