Guillermo Fuentes
Guillermo Fuentes

Reputation: 39

How to make an observe at the moment in Swift?

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

Answers (1)

Levi Yoder
Levi Yoder

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

Related Questions