For in loopを使ってFirebaseに大量のネットワークリクエストを送信し、メソッドの実行が完了したらデータを新しいView Controllerに渡します。これが私のコードです:
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を次のView Controllerに渡す前に、すべての非同期リクエストが完了するまで待つだけです。
すべてのリクエストが終了したときに非同期コールバックを起動するために 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.")
})
}
Xcode 8.3.1 - Swift
これは、Swift 3に変換された、paulvsの一般的な回答です。
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.")
})
}
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()
}
}
}
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() }
}
}
あなたはこの目的のためにセマフォを使う必要があるでしょう。
//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.
Swift 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...
Swift 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 ...")
これは再帰で行うことができます。以下のコードからアイデアを得る:
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()
}
}
}