Reputation: 39
I have a problem in this code:
func calculaGastos() -> Float{
let idUser = Auth.auth().currentUser?.displayName
var total : Float = 0
let ref = Database.database().reference().child("Users").child(idUser!).child("Gastos")
ref.observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? NSDictionary
if(value != nil){
for i in value!{
let j = i.value as? NSDictionary
let precio = j?["precio"] as? Float
let fecha = j?["Fecha"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let date = dateFormatter.date(from: fecha!)
if(((date?.timeIntervalSinceNow)! * -1) < (30*24*3600)){
print("Total:" ,total)
total += precio!
}
}
}
print("Calculing in the function", total)
}
return total
}
Which is called in a override function in another view controller and the logs shows that in the print of the viewdidload is 0, but in the function print is show that is printed as 30 but return 0 all time, I believe that the problem is that it returns before enter in the observer but I'm not sure any solutions for this?
override func viewDidLoad() {
super.viewDidLoad()
nomUser.text = id?.displayName!
correoLabel.text = id?.email!
print("Calculing in View Controller", calculo.calculaBenef(), calculo.calculaGastos())
gastosField.text = String(calculo.calculaGastos())
benefField.text = String(calculo.calculaBenef())
// Do any additional setup after loading the view.
}
Here is my log: Log
Upvotes: 1
Views: 78
Reputation: 177
Within an app I'm currently working on, I ran into a similar issue. The solution was to implement a dispatch group in the function. I also changed the way your function returns total
so that it's now being returned by a completion handler.
Try this instead:
func calculaGastos(completionHandler: @escaping (Float) -> Void){
let idUser = Auth.auth().currentUser?.displayName
var total : Float = 0
let ref = Database.database().reference().child("Users").child(idUser!).child("Gastos")
ref.observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? NSDictionary
if(value != nil){
let myGroup = DispatchGroup()
for i in value!{
myGroup.enter()
let j = i.value as? NSDictionary
let precio = j?["precio"] as? Float
let fecha = j?["Fecha"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let date = dateFormatter.date(from: fecha!)
if(((date?.timeIntervalSinceNow)! * -1) < (30*24*3600)){
print("Total:" ,total)
total += precio!
}
myGroup.leave()
}
myGroup.notify(queue: .main) {
print("Calculing in the function", total)
completionHandler(total)
}
}}
}
Call the function and use total
like this:
override func viewDidLoad() {
super.viewDidLoad()
nomUser.text = id?.displayName!
correoLabel.text = id?.email!
print("Calculing in View Controller", calculo.calculaBenef())
calculo.calculaGastos { (total) in
print("calculaGastos total: \(total)")
gastosField.text = String(total)
benefField.text = String(calculo.calculaBenef())
}
// Do any additional setup after loading the view.
}
To my understanding:
observeSingleEvent
is asynchronous, so it may or may not complete by the time return
is called. Additionally, the for i in value
starts only after observeSingleEvent
is complete, so return
is even more likely to be called before the tasks are completed. That's where DispatchGroup()
and the completion handler come in.
When myGroup.enter()
is called, it notifies DispatchGroup that a task has started. When myGroup.leave()
is called, DispatchGroup is notified that the task has been completed. Once there have been as many .leave()
s as .enter()
s, the group is finished. Then myGroup
notifies the main queue that the the group is finished, and then the completionHandler
is called which returns total.
The completionHandler is also beneficial because of the way in which you're using calculaGastos
. You're calling the function, and then you're using the return value to be displayed in a textField. Now that the completion handler is added, the textField.text
is only being set after calculaGastos
is complete and has returned total
:
calculo.calculaGastos { (total) in
print("calculaGastos total: \(total)")
gastosField.text = String(total)
benefField.text = String(calculo.calculaBenef())
}
Hope that makes some sense! Glad the code worked for you.
Upvotes: 1