swift - bar - uinavigationitem title color
Espere hasta que el bucle rápido con solicitudes de red asincrónicas termine de ejecutarse (9)
Me gustaría un for in loop para enviar un montón de solicitudes de red a firebase, luego pasar los datos a un nuevo controlador de vista una vez que el método termine de ejecutarse. Aquí está mi código:
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
Tengo un par de preocupaciones Primero, ¿cómo espero hasta que finalice el ciclo for y se completen todas las solicitudes de red? No puedo modificar la función observeSingleEventOfType, es parte del SDK de Firebase. Además, ¿crearé algún tipo de condición de carrera al intentar acceder a la matriz de fechas desde diferentes iteraciones del ciclo for (espero que tenga sentido)? He estado leyendo sobre GCD y NSOperation pero estoy un poco perdido ya que esta es la primera aplicación que he creado.
Nota: La matriz de ubicaciones es una matriz que contiene las claves a las que necesito acceder en firebase. Además, es importante que las solicitudes de red se disparen de forma asincrónica. Solo quiero esperar hasta que se completen TODAS las solicitudes asincrónicas antes de pasar el arreglo de fechas al siguiente controlador de vista.
Detalles
- Xcode 10.2.1 (10E1001), Swift 5
Solución
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() }
}
}
}
}
Uso
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...
}
Muestra completa
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() }
}
}
Deberá utilizar semáforos para este propósito.
//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.
El grupo de despacho es bueno pero el orden de las solicitudes enviadas es aleatorio.
Finished request 1
Finished request 0
Finished request 2
En el caso de mi proyecto, cada solicitud que se necesita para iniciar es el orden correcto. Si esto podría ayudar a alguien:
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)
}
}
Llamada :
trySendRequestsNotSent()
Resultado:
Send list started
Send request 0
Receive request 0
Send request 1
Receive request 1
Send request 2
Receive request 2
...
Send list finished
Ver para más información: Gist
Podemos hacer esto con recursión. Obtenga una idea del siguiente código:
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()
}
}
}
Puede usar grupos de despacho para disparar una devolución de llamada asíncrona cuando finalicen todas sus solicitudes.
Aquí hay un ejemplo en Swift 4.1 (también funciona en Swift 3) usando grupos de despacho para ejecutar una devolución de llamada de forma asincrónica cuando varias solicitudes de red han finalizado.
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.")
}
}
Salida
Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.
Para aquellos que usan el Swift 2.3 anterior, aquí hay un ejemplo usando su sintaxis:
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.")
})
}
Usando swift 5:
Intenta ejecutar este código en el patio de recreo. Puedes jugar con resultados ordenados y desordenados
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 ...")
Swift 3 o 4
Si no le importan los pedidos , use la answer de @paulvs, funciona perfectamente.
de lo contrario, en caso de que alguien quiera ordenar el resultado en lugar de dispararlo al mismo tiempo, here está el código.
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()
}
}
}
Swift 3: también puede usar semáforos de esta manera. Resulta muy útil, además de que puede realizar un seguimiento exacto de cuándo y qué procesos se completan. Esto ha sido extraído de mi código:
//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...
Xcode 8.3.1 - Swift 3
Esta es la respuesta aceptada de paulvs, convertida a 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.")
})
}