Reputation: 899
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:
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.
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?
Upvotes: 24
Views: 48075
Reputation: 25294
xCode 9.2, 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
// sync/async action
// ...
// action complete
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()
}
}
}
}
Upvotes: 2
Reputation: 919
two ways to solve this, both use Grand Central Dispatch (which is similar in Swift and Objective C):
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
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.
Upvotes: 3
Reputation: 50129
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")
}
add an intermediate completionHandler
func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) {
loadShows() {
completionHandler(UIBackgroundFetchResult.NewData)
println("Background Fetch Complete")
}
}
func loadShows(completionHandler: (() -> Void)!) {
//....
//DO IT
//....
completionHandler()
}
Upvotes: 33