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中类似):
-
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,就像在同步程序中一样
-
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
#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中类似):
-
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,就像在同步程序中一样
-
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
#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))
}