Je voudrais qu'une boucle for in envoie un ensemble de requêtes réseau à Firebase, puis transmette les données à un nouveau contrôleur de vue une fois que la méthode a fini de s'exécuter. Voici mon code:
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
J'ai quelques préoccupations. Premièrement, comment puis-je attendre que la boucle for soit terminée et que toutes les demandes du réseau soient terminées? Je ne peux pas modifier la fonction observeSingleEventOfType, elle fait partie du kit SDK de Firebase. Aussi, vais-je créer une sorte de condition de concurrence en essayant d'accéder au datesArray à partir de différentes itérations de la boucle for (j'espère que cela a un sens)? J'ai lu sur GCD et NSOperation mais je suis un peu perdu car c'est la première application que j'ai construite.
Remarque: Le tableau Locations est un tableau contenant les clés auxquelles j'ai besoin d'accéder dans Firebase. De plus, il est important que les requêtes réseau soient lancées de manière asynchrone. Je veux juste attendre que TOUTES les requêtes asynchrones soient terminées avant de transmettre le datesArray au prochain contrôleur de vue.
Vous pouvez utiliser dispatch group pour déclencher un rappel asynchrone lorsque toutes vos demandes se terminent.
Voici un exemple dans Swift 4.1 (fonctionne également dans Swift 3) en utilisant des groupes de répartition pour exécuter un rappel de manière asynchrone lorsque plusieurs demandes de mise en réseau sont terminées.
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.")
}
}
Sortie
Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.
Pour ceux qui utilisent l'ancien Swift 2.3, voici un exemple utilisant sa syntaxe:
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
C’est la réponse acceptée de paulvs, converti en 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.")
})
}
Swift 3 ou 4
Si vous n'aimez pas les commandes , utilisez les @ paulvs answer ça marche parfaitement.
sinon, au cas où quelqu'un voudrait obtenir le résultat dans l'ordre au lieu de les renvoyer simultanément, ici est le code.
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() }
}
}
Vous devrez utiliser des sémaphores à cette fin.
//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: Vous pouvez également utiliser des sémaphores de cette façon. Cela s'avère très utile, mais vous pouvez également savoir exactement quand et quels processus sont terminés. Ceci a été extrait de mon code:
//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...
Utiliser Swift 5:
Essayez d'exécuter ce code dans la cour de récréation. Vous pouvez jouer avec des résultats ordonnés et non ordonnés
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 ...")
Nous pouvons le faire avec la récursivité. Obtenez une idée du code ci-dessous:
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()
}
}
}