我希望for循环向firebase发送一堆网络请求,然后在方法完成执行后将数据传递给新的视图控制器。这是我的代码:
var datesArray = [String: AnyObject]()
for key in locationsArray {
let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
datesArray["\(key.0)"] = snapshot.value
})
}
// Segue to new view controller here and pass datesArray once it is complete
我有几个问题。首先,我如何等待for循环完成并且所有网络请求都已完成?我无法修改observeSingleEventOfType函数,它是firebase SDK的一部分。另外,我是否会通过尝试从for循环的不同迭代中访问datesArray来创建某种竞争条件(希望有意义)?我一直在阅读有关GCD和NSOperation的内容,但我有点迷失,因为这是我建立的第一个应用程序。
注意:Locations数组是一个包含我需要在firebase中访问的键的数组。此外,重要的是网络请求是异步触发的。我只想等到所有异步请求完成后再将datesArray传递给下一个视图控制器。
答案 0 :(得分:263)
当您的所有请求完成后,您可以使用dispatch groups触发异步回调。
以下是Swift 4.1中的一个示例(也适用于Swift 3),当多个网络请求都已完成时,使用调度组异步执行回调。
override func viewDidLoad() {
super.viewDidLoad()
let myGroup = DispatchGroup()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
myGroup.leave()
}
}
myGroup.notify(queue: .main) {
print("Finished all requests.")
}
}
<强>输出强>
Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.
对于那些使用旧版Swift 2.3的人来说,这是一个使用其语法的例子:
override func viewDidLoad() {
super.viewDidLoad()
let myGroup = dispatch_group_create()
for i in 0 ..< 5 {
dispatch_group_enter(myGroup)
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
dispatch_group_leave(self.myGroup)
}
}
dispatch_group_notify(myGroup, dispatch_get_main_queue(), {
print("Finished all requests.")
})
}
答案 1 :(得分:39)
Xcode 8.3.1 - Swift 3
这是paulvs的公认答案,转换为Swift 3:
let myGroup = DispatchGroup()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
myGroup.leave()
}
}
myGroup.notify(queue: DispatchQueue.main, execute: {
print("Finished all requests.")
})
}
答案 2 :(得分:20)
Swift 3或4
如果您不关心订单,请使用@ paulvs的answer,它运作正常。
否则万一有人想要按顺序获取结果而不是同时触发它们,here就是代码。
let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "any-label-name")
let dispatchSemaphore = DispatchSemaphore(value: 0)
dispatchQueue.async {
// use array categories as an example.
for c in self.categories {
if let id = c.categoryId {
dispatchGroup.enter()
self.downloadProductsByCategory(categoryId: id) { success, data in
if success, let products = data {
self.products.append(products)
}
dispatchSemaphore.signal()
dispatchGroup.leave()
}
dispatchSemaphore.wait()
}
}
}
dispatchGroup.notify(queue: dispatchQueue) {
DispatchQueue.main.async {
self.refreshOrderTable { _ in
self.productCollectionView.reloadData()
}
}
}
答案 3 :(得分:15)
import Foundation
class SimultaneousOperationsQueue {
typealias CompleteClosure = ()->()
private let dispatchQueue: DispatchQueue
private lazy var tasksCompletionQueue = DispatchQueue.main
private let semaphore: DispatchSemaphore
var whenCompleteAll: (()->())?
private lazy var numberOfPendingActionsSemaphore = DispatchSemaphore(value: 1)
private lazy var _numberOfPendingActions = 0
var numberOfPendingTasks: Int {
get {
numberOfPendingActionsSemaphore.wait()
defer { numberOfPendingActionsSemaphore.signal() }
return _numberOfPendingActions
}
set(value) {
numberOfPendingActionsSemaphore.wait()
defer { numberOfPendingActionsSemaphore.signal() }
_numberOfPendingActions = value
}
}
init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) {
dispatchQueue = DispatchQueue(label: dispatchQueueLabel)
semaphore = DispatchSemaphore(value: numberOfSimultaneousActions)
}
func run(closure: ((@escaping CompleteClosure) -> Void)?) {
numberOfPendingTasks += 1
dispatchQueue.async { [weak self] in
guard let self = self,
let closure = closure else { return }
self.semaphore.wait()
closure {
defer { self.semaphore.signal() }
self.numberOfPendingTasks -= 1
if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
self.tasksCompletionQueue.async { closure() }
}
}
}
}
func run(closure: (() -> Void)?) {
numberOfPendingTasks += 1
dispatchQueue.async { [weak self] in
guard let self = self,
let closure = closure else { return }
self.semaphore.wait(); defer { self.semaphore.signal() }
closure()
self.numberOfPendingTasks -= 1
if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
self.tasksCompletionQueue.async { closure() }
}
}
}
}
let queue = SimultaneousOperationsQueue(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString")
queue.whenCompleteAll = { print("All Done") }
// add task with sync/async code
queue.run { completeClosure in
// your code here...
// Make signal that this closure finished
completeClosure()
}
// add task only with sync code
queue.run {
// your code here...
}
import UIKit
class ViewController: UIViewController {
private lazy var queue = { SimultaneousOperationsQueue(numberOfSimultaneousActions: 1,
dispatchQueueLabel: "AnyString") }()
private weak var button: UIButton!
private weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 50, y: 80, width: 100, height: 100))
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.numberOfLines = 0
view.addSubview(button)
self.button = button
let label = UILabel(frame: CGRect(x: 180, y: 50, width: 100, height: 100))
label.text = ""
label.numberOfLines = 0
label.textAlignment = .natural
view.addSubview(label)
self.label = label
queue.whenCompleteAll = { [weak self] in self?.label.text = "All tasks completed" }
//sample1()
sample2()
}
func sample1() {
button.setTitle("Run 2 task", for: .normal)
button.addTarget(self, action: #selector(sample1Action), for: .touchUpInside)
}
func sample2() {
button.setTitle("Run 10 tasks", for: .normal)
button.addTarget(self, action: #selector(sample2Action), for: .touchUpInside)
}
private func add2Tasks() {
queue.run { completeTask in
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(1)) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
}
completeTask()
}
}
queue.run {
sleep(1)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
}
}
}
@objc func sample1Action() {
label.text = "pending tasks \(queue.numberOfPendingTasks)"
add2Tasks()
}
@objc func sample2Action() {
label.text = "pending tasks \(queue.numberOfPendingTasks)"
for _ in 0..<5 { add2Tasks() }
}
}
答案 4 :(得分:6)
为此,您需要使用信号量。
//Create the semaphore with count equal to the number of requests that will be made.
let semaphore = dispatch_semaphore_create(locationsArray.count)
for key in locationsArray {
let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
datesArray["\(key.0)"] = snapshot.value
//For each request completed, signal the semaphore
dispatch_semaphore_signal(semaphore)
})
}
//Wait on the semaphore until all requests are completed
let timeoutLengthInNanoSeconds: Int64 = 10000000000 //Adjust the timeout to suit your case
let timeout = dispatch_time(DISPATCH_TIME_NOW, timeoutLengthInNanoSeconds)
dispatch_semaphore_wait(semaphore, timeout)
//When you reach here all request would have been completed or timeout would have occurred.
答案 5 :(得分:4)
斯威夫特3: 你也可以用这种方式使用信号量。它非常有用,除了可以准确跟踪何时以及完成哪些过程。这是从我的代码中提取的:
//You have to create your own queue or if you need the Default queue
let persons = persistentContainer.viewContext.persons
print("How many persons on database: \(persons.count())")
let numberOfPersons = persons.count()
for eachPerson in persons{
queuePersonDetail.async {
self.getPersonDetailAndSave(personId: eachPerson.personId){person2, error in
print("Person detail: \(person2?.fullName)")
//When we get the completionHandler we send the signal
semaphorePersonDetailAndSave.signal()
}
}
}
//Here we will wait
for i in 0..<numberOfPersons{
semaphorePersonDetailAndSave.wait()
NSLog("\(i + 1)/\(persons.count()) completed")
}
//And here the flow continues...
答案 6 :(得分:1)
由于这个问题不是针对 Firebase 或 Alamofire,我想为 Swift 5.5 和 iOS 15+ 添加一个更现代的解决方案。
下面的答案使用了 async / await
和 Structured Concurrency
。下面概述的方法是 Apple 现在推荐用于并发请求的方法。
此答案将帮助过去将 URLSession
请求排队并等待这些请求全部完成的用户。
如果我们有动态数量的请求(可变大小的数组),正确的工具是 Task
组。
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
try await withThrowingTaskGroup(of: (String, UIImage).self) { group in
for id in ids {
group.async {
return (id, try await fetchOneThumbnail(withID: id))
}
}
for try await (id, thumbnail) in group {
thumbnails[id] = thumbnail
}
}
return thumbnails
}
这也使用 for await
循环 (AsyncSequence
) 来等待任务完成。 for try await
是一个投掷 AsyncSequence
的例子。抛出语法是因为新的异步 URLSession.data(for:)
系列方法是抛出函数。
async let
示例代码此语法适用于固定数量的请求。
let reqOne = urlRequest(for: keyOne) // Function that returns a unique URLRequest object for this key. i.e. different URLs or format.
async let (dataOne, _) = URLSession.shared.data(for: reqOne)
let reqTwo = urlRequest(for: keyTwo)
async let (dataTwo, _) = URLSession.shared.data(for: reqTwo)
guard let parsedData = parseInformation(from: try? await dataOne) else {
// Call function to parse image, text or content from data.
continue
}
// Act on parsed data if needed.
guard let parsedDataTwo = parseInformation(from: try? await dataTwo) else {
// Call function to parse image, text or content from data.
continue
}
// Act on the second requests parsed data if needed.
// Here, we know that the queued requests have all completed.
我不为请求立即完成 await
的语法称为 async let
。
此代码示例可适用于可变大小的数组,但 Apple 不推荐使用。这是因为 async let
并不总是允许在请求到达时立即对其进行处理。
此外,代码更易于编写、更安全并避免死锁。
TaskGroup
和 async let
的确切语法将来可能会发生变化。目前,async / await
和 Structured Concurrency 正在测试阶段获得反馈。
然而,Apple 已经明确表示,分组和异步任务的底层机制大多已经完成(在 Swift Evolution 中得到批准)。一些语法更改的示例已经包括将 async {
替换为 Task {
。
答案 7 :(得分:0)
使用迅速5:
尝试在操场上运行此代码。您可以播放有序和无序结果
let dispatchGroup:DispatchGroup = DispatchGroup()
let semaphore = DispatchSemaphore(value: 1)
let globalQueue = DispatchQueue(label: "globalQueue")
let innerQueue = DispatchQueue(label: "innerQueue")
func doLongThing(i:Int, completion:@escaping ()->Void){
print("In \(i)")
innerQueue.sync {
sleep(2)
print("Out \(i)")
completion()
}
}
globalQueue.async {
for i in 0..<4{
dispatchGroup.enter()
doLongThing(i: i) {
print("Finished \(i)")
semaphore.signal()
}
semaphore.wait()
dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
print("ALL Done")
}
}
print("In the meantime ...")
答案 8 :(得分:0)
我们可以通过递归来做到这一点。 从下面的代码中了解想法:
var count = 0
func uploadImages(){
if count < viewModel.uploadImageModelArray.count {
let item = viewModel.uploadImageModelArray[count]
self.viewModel.uploadImageExpense(filePath: item.imagePath, docType: "image/png", fileName: item.fileName ?? "", title: item.imageName ?? "", notes: item.notes ?? "", location: item.location ?? "") { (status) in
if status ?? false {
// successfully uploaded
}else{
// failed
}
self.count += 1
self.uploadImages()
}
}
}
答案 9 :(得分:-1)
调度组很好,但发送请求的顺序是随机的。
rbind(setDT(df1), df1[, Date := as.Date(Date, "%Y.%m.%d")][,
.SD[.N], ID][, Date := seq(Date, length.out = 2, by = 'month')[2], by = ID])[order(ID)]
在我的项目案例中,每个需要启动的请求都是正确的顺序。如果这可以帮助某人:
Finished request 1
Finished request 0
Finished request 2
致电:
public class RequestItem: NSObject {
public var urlToCall: String = ""
public var method: HTTPMethod = .get
public var params: [String: String] = [:]
public var headers: [String: String] = [:]
}
public func trySendRequestsNotSent (trySendRequestsNotSentCompletionHandler: @escaping ([Error]) -> () = { _ in }) {
// If there is requests
if !requestItemsToSend.isEmpty {
let requestItemsToSendCopy = requestItemsToSend
NSLog("Send list started")
launchRequestsInOrder(requestItemsToSendCopy, 0, [], launchRequestsInOrderCompletionBlock: { index, errors in
trySendRequestsNotSentCompletionHandler(errors)
})
}
else {
trySendRequestsNotSentCompletionHandler([])
}
}
private func launchRequestsInOrder (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], launchRequestsInOrderCompletionBlock: @escaping (_ index: Int, _ errors: [Error] ) -> Void) {
executeRequest(requestItemsToSend, index, errors, executeRequestCompletionBlock: { currentIndex, errors in
if currentIndex < requestItemsToSend.count {
// We didn't reach last request, launch next request
self.launchRequestsInOrder(requestItemsToSend, currentIndex, errors, launchRequestsInOrderCompletionBlock: { index, errors in
launchRequestsInOrderCompletionBlock(currentIndex, errors)
})
}
else {
// We parse and send all requests
NSLog("Send list finished")
launchRequestsInOrderCompletionBlock(currentIndex, errors)
}
})
}
private func executeRequest (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], executeRequestCompletionBlock: @escaping (_ index: Int, _ errors: [Error]) -> Void) {
NSLog("Send request %d", index)
Alamofire.request(requestItemsToSend[index].urlToCall, method: requestItemsToSend[index].method, parameters: requestItemsToSend[index].params, headers: requestItemsToSend[index].headers).responseJSON { response in
var errors: [Error] = errors
switch response.result {
case .success:
// Request sended successfully, we can remove it from not sended request array
self.requestItemsToSend.remove(at: index)
break
case .failure:
// Still not send we append arror
errors.append(response.result.error!)
break
}
NSLog("Receive request %d", index)
executeRequestCompletionBlock(index+1, errors)
}
}
结果:
trySendRequestsNotSent()
查看更多信息: Gist