0

So I have a function I have that has an inner function that connects to a Firebase database. the function returns a bool but I need the to return true or false based on whats inside the Firebase database. So basically I need to return true or false inside of the Firebase function the problem is it thinks that I am trying to return to the Firebase function when I am actually trying to return to the outside function a simple example is this

func someOtherFunc(name: String, lastname: String){


}
func someFunc(name: String, lastname: String) -> bool {
    someOtherFunc(name: name, lastname: lastname) {
        if name == "George" {
        return true


        } else {
        return false // will not work because some other func does not return a value
        }


    }
}

here is the code that I need fixed which is a little more complicated than the above function because the inner Firebase function runs asynchronously(in the background) and so all the code inside the function needs to run synchronously to return the correct value

Here is my function

func takeAwayMoney(_ howMuch: String) -> Bool{
        if let notMuch = Int(howMuch) {

            let userID = FIRAuth.auth()?.currentUser?.uid
            datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
                // Get user value
                let value = snapshot.value as? NSDictionary
                let money = value?["money"] as? String ?? ""

                //convert money to int
                if let conMoney = Int(money) {
                    var conMoreMoney = conMoney
                    if conMoreMoney < notMuch {
                        print(" sorry you don't have enough money")
                        return false
                    } else {
                        conMoreMoney -= notMuch
                        let values = ["money": String(conMoreMoney)]

                        //update the users money
                        self.datRef.child("User").child(userID!).updateChildValues(values)
                        return true //doesn't work because of example above
                    }

                }

                // ...
            }) { (error) in
                print(error.localizedDescription)
            }
        }
    }

This code does not compile because there is no return values to the main function.

I know the really hard way to fix this would be to initialize values at the top of the function that would be the money of the user then check it after dispatching it for a couple of seconds then you could return the values, but I know there must be another way because this way would cause a lot of problems.

AL.
  • 36,815
  • 10
  • 142
  • 281
Devin Tripp
  • 115
  • 13
  • 1
    Possible duplicate of [Can Swift return value from an async Void-returning block?](http://stackoverflow.com/questions/28390635/can-swift-return-value-from-an-async-void-returning-block) – rmaddy Mar 29 '17 at 23:45
  • Firebase is asynchronous and trying to return values (even with additional completion handles, threading etc) is going to really overcomplicate things and require a bunch of additional code. The simple solution is to re-think why you want to return true or false and how that returned value factors into the rest of your code. In this case, the code checks to see how much money the user has and takes action based on that - the returned value isn't being used. Update the UI and then still within the closure, proceed to the next step if there is one. That will provide a smooth user experience. – Jay Mar 30 '17 at 21:14

1 Answers1

1

The root cause is that takeAwayMoney is synchronous, but it uses observeSingleEvent, which is asynchronous.

The "right" way to solve this is to make takeAwayMoney return Void, but take a completion function that will give you the bool asynchronously, like this:

func takeAwayMoney(_ howMuch: String, completion: @escaping (Bool)->()) -> Void {

/// When you want to "return" the bool, call the completion. e.g.:
// ...
                if conMoreMoney < notMuch {
                    print(" sorry you don't have enough money")
                    completion(false)
                    return // if you want to stop processing
                }
// ...

}

If you cannot do this, then takeMoney needs to wait for the completion to finish and you use semaphores to have them communicate with each other. You do not just wait a couple of seconds. See this for an example:

Semaphores to run asynchronous method synchronously

Community
  • 1
  • 1
Lou Franco
  • 87,846
  • 14
  • 132
  • 192
  • This sort of question has been asked many, many times before. Better to close as a dupe than post yet another answer. – rmaddy Mar 29 '17 at 23:59
  • ok ok but this brings up another question when i call the funciton which would be something like takeAwayMoney("20", completion: { (result: Bool) in guard let returnedResult = result else { return } //does this part run sychronously with the function or does it run sychronously with the main thread? }) I need to know where to put the code that is dependent on that bool value that is returned in the completion. @LouFranco – Devin Tripp Mar 30 '17 at 00:06
  • If you ultimately need to make it synchronous then you need to check out the duplicate question or the one I posted. – Lou Franco Mar 30 '17 at 00:07
  • It is called by the thread that observeSingleEvent responds on. If you need it to be the main thread, use dispatch_async to the main thread to do that. Everything will be asynchronous. – Lou Franco Mar 30 '17 at 00:18
  • could you just tell me how to run code sycrhonously with the firebase – Devin Tripp Mar 30 '17 at 00:18
  • dispatch_async is depreciated do you know how to pause the main method in swift three? I have also been searching for this as a solution. @LouFranco it would be DispatchQue.main.something – Devin Tripp Mar 30 '17 at 00:20
  • `DispatchQueue.main.async { /* your code here */ }` . This does not pause the main thread. This calls the code in the {} on the main thread. – Lou Franco Mar 30 '17 at 00:23
  • Just use a semaphore to have takeMoney wait and then return the bool: http://stackoverflow.com/questions/34858307/semaphores-to-run-asynchronous-method-synchronously – Lou Franco Mar 30 '17 at 00:24
  • is there a swift 3 version that's in OBJ C which I have tried to convert before couldn't understand it – Devin Tripp Mar 30 '17 at 00:41
  • I would suggest this answer, while really great information, is not the only answer and may be sending the OP in the wrong direction in the future. The big picture is that Firebase functions are asynchronous and code shouldn't be relying on those functions to return values. Yeah... it can be done but it requires addtional callbacks, completion handlers, threading, semaphores etc - basically a lot of extra code. The simple solution is to get the data from Firebase and update the UI with the message *within the closure* once the data is available. – Jay Mar 30 '17 at 21:08