Reputation: 45
I have the following function and it's working great but now I need the value out of the function to use it for another purpose. But my problem is that I always have to execute the function twice to get a value in the outer part of the function (var valueOutOfFunction).
var valueOutOfFunction = [(String)]()
func loadQuery(com:@escaping( [(Int, String)] -> ())){
var arrayOfTuples = [(Int, String)]()
db.collection("Data").whereField("age", isGreaterThanOrEqualTo: 1).whereField("age", isLessThanOrEqualTo: 50).whereField("gender", isEqualTo: "F").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for (index, document) in querySnapshot!.documents.enumerated() {
arrayOfTuples += [(index, document.documentID)]
}
}
com(arrayOfTuples)
}
}
Then I am calling it here:
loadQuery { arr in
self.valueOutOfFunction = arr // Has a value in the first execution
}
print (valueOutOfFunction) //Executing the first time there is no value in the variable, the second time it have a value.
Why is it just on the second attempt available and what could be a solution for this problem ? Thanks!
Upvotes: 0
Views: 49
Reputation: 2216
That is because the valueOutOfFunction
is happening inside of that closure which is being called asynchronously to the print statement. Additionally, you are executing the completion outside of the else statement which may be firing before your for loop completes. What you want to do is control the flow from within the closure itself like so:
// move com(arrayOfTuples) up into the else block but outside of the for loop
func handleQuery() {
loadQuery { doStuffWithResult($0) }
// This line will run before the first line of doStuffWithResult
// It would be best to end the function logic at loadQuery
}
func doStuffWithResult(_ arrayOfTuples: [(Int, String)]) {
print(arrayOfTuples)
// Do other work here
}
What you're looking into is control flow. You want to execute X if Y
has occurred and Z if !Y
right?
I would recommend looking into swift's result type. This will help you manage control flow with closures.
An example:
typealias QueryResult = [(Int, String)]
enum QueryErrors: Error {
case couldNotFind
}
func loadQuery(_ result: @escaping (Result<QueryResult, Error>) -> Void) {
db.collection("Data").whereField("age", isGreaterThanOrEqualTo: 1).whereField("age", isLessThanOrEqualTo: 50).whereField("gender", isEqualTo: "F").getDocuments() { (querySnapshot, err) in
guard let documents = querySnapshot?.documents.enumerated() else {
// I think enumerated will give you an array of tuples... if not add this to the end of that line. After enumerated but before else
// enumerated().compactMap { ($0, $1) }
result(.failure(err ?? QueryErrors.couldNotFind))// passes back Firebase error if it exists or default error if it doesn't
return
}
result(.success(documents))
}
}
// how it works
loadQuery() { result in
switch(result) {
case .success(let arr): print(arr)
case .failure(let error): print(error)
}
}
Upvotes: 2