This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
the following is printing "no user with username" but is printing retVal as "false" ( I changed function to a string just for troubleshooting, ideally this should be bool ) I am new to swift and this is absolutely driving me crazy. it is making it to the chunk of code where retVal would get reassigned, but it isn't reassigning it
static func isUserNameUnique(_ username : String) -> String {
var retVal = "false"
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
if document!.isEmpty {
retVal = "true"
print("No user with username")
}
}
print("\(retVal)")
return retVal
}
func validateFields() -> String? {
//Check that all fields are filled in
if premierCodeTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || userNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
return "Please fill in all fields."
}
//Check unique username
let cleanedUserName = userNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Utilities.isUserNameUnique(cleanedUserName) { res in
if !res {
// return "please choose a unique username"
}
}
return nil
}
You are trying to return a value synchronously while using and asynchronous method.
When you do newQuery.getDocuments execution continues without waiting for completion handler to be called. So after that line is executed, the return is executed, and THEN the completion handler gets called.
If you want to get a value from an asynchronous method, you need to create a method that takes a completion handler like the answer Khan gave you.
static func isUserNameUnique(_ username: String, completionHandler: #escaping (Bool) -> Void) {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
completionHandler(document!.isEmpty)
}
}
You need a completion as the request is asynchnous , plus use Bool instead of a String
static func isUserNameUnique(_ username : String,completion:#escaping((Bool) ->())) {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
completion(document!.isEmpty)
}
}
Call
Utilities.isUserNameUnique { res in
if !res {
// name exists
}
}
It's impossible to achieve what you want since newQuery.getDocuments isn't returning value instantly. It will answer you at some point by calling function that you passed to it.
Your code can be described as
func foo() -> String {
// set retVal to "false"
var retVal = "false"
// create query
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
// ask query to evaluate
newQuery.getDocuments { (document, error) in
// at some point probably after foo ends
if document!.isEmpty {
// if document is not empty set retVal to "true" (at this point there is no-one that could look at value of retVal)
retVal = "true"
print("No user with username")
}
}
// while query is evaluating in background
// print retVal (probably still "false")
print("\(retVal)")
// return retVal (probably still "false")
return retVal
}
Now let's fix your problem.
Solution could be:
class X {
private var document: <insert correct type here>? {
didSet {
// do what you want with document
}
}
func foo() {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments {
[weak self] (document, error) in // [weak self] is important!
// I have no idea on which thread firebase runs it's callback
// It's important that only one thread could modify self.document
// otherwise you will have race condition and a lot of strange behaviours
DispatchQueue.main.async {
self?.document = document;
}
}
}
}
If you really need to create func foo() -> String and you don't care that your thread will have to wait (UI will not respond, you will have 0 fps etc) you can do it using NSLock (I won't post code since it's really bad idea in most of the cases).
Related
Hi I am new in iOS development and I am having hard time to understand the following issue. Basically I am trying to get user's name by passing current user's id to Cloud Firestore. However I am having hard time to understand a bug in the code. I can successfully pass the name of user to name variable, while the function returns default value of name which is "" (empty string). It seems that the block of code inside
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
print("after guard") // this line
}
happens later than
print("name") // this line
return name
Full code:
private func returnCurrentUserName() -> String {
// User is signed in.
var name = ""
if let user = Auth.auth().currentUser {
let db = Firestore.firestore()
db.collection("users").document(user.uid).getDocument { (snapshot, error) in
if error == nil {
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
print("after guard") // this line
}
}
}
print("name") // this line
return name
}else {
return ""
}
}
(Note: the query from Cloud Firestore is successful and I can get users name on the console but "name" is printed after "after guard".)
In addition to the other answer:
If you would like to execute code after your operation is done, you could use a completion block (that's just a closure which gets called upon completion):
private func returnCurrentUserName(completion: #escaping () -> ()) -> String {
// User is signed in.
var name = ""
if let user = Auth.auth().currentUser {
let db = Firestore.firestore()
db.collection("users").document(user.uid).getDocument { (snapshot, error) in
if error == nil {
if let data = snapshot?.data() {
guard let userName = data["name"] as? String else { return }
name = userName
completion()//Here you call the closure
print("after guard") // this line
}
}
}
print("name") // this line
return name
}else {
return ""
}
}
How you would call returnCurrentUserName:
returnCurrentUserName {
print("runs after the operation is done")
}
Simplified example:
func returnCurrentUserName(completion: #escaping () -> ()) -> String {
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
completion() //runs after 4 seconds
}
return "xyz"
}
let test = returnCurrentUserName {
print("runs after the operation is done")
}
print(test)
The reason is your getDocument is an asynchronous operation. It takes a callback, and that callback will be invoked when the operation is done. Because of the asynchronous operation, the program will continue process the next line without waiting for the async operation to be completed. That's why you see your print("name") getting executed before the print("after guard")
I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)
I'm actually trying to some code logic with swift for training purpose at the moment, I was wondering what is the proper way to throw my error from my init ?
So the flow is Controller ask for account creation when initializing Model is asking my sql manager to create the account and this method return the result from a closure.
But something feels wrong, should I just use a return from the sql manager who contained both my Int? and Error? ?
init(_ username: String, _ password: String) throws {
self.id = 0
self.username = username
self.password = password
var toThrow: Error? = nil
// Register in database
userManager.create(self) { (id: Int?, err: Error?) in
Thread.sleep(forTimeInterval: 10)
if let error = err {
// Register in database goes wrong
debugPrint("Handle error from user creation...")
toThrow = error
} else {
// There is no id and no error ?
guard let _ = id else { return }
self.id = id!
}
}
if let error = toThrow {
throw error
}
}
If you are on Swift 5 you could look into using Result and define your closure like
(id: Int) -> Result<Int, Error>
and change your code to
userManager.create(self) { (id: Int?) -> Result<Int, Error> in
Thread.sleep(forTimeInterval: 10)
if let error = err {
// Register in database goes wrong
debugPrint("Handle error from user creation...")
return .failure(error)
} else {
// There is no id and no error ?
guard let _ = id else { return }
return .success(id)
}
}
If you have your own error enum for the Db class like
enum DbError {
case create
case update
//...
}
Then you can use that type in the closure declaration
(id: Int?) -> Result<Int, DbError>
and return a specific error for this action
return .failure(.create)
Note that I haven't compiled this so consider it an example
Here is the solution if people want to see:
#IBAction func didPressRegister() {
guard let username = usernameField.text else { return }
guard let password = passwordField.text else { return }
let user = UserModel(username, password)
userManager.create(user) { result in
switch(result) {
case .failure(let error):
// TODO: UIAlert
debugPrint(error)
case .success(let int):
// TODO: Generate user token and redirect main
debugPrint(int)
}
}
}
// TODO
public func create(_ user: UserModel, _ complete: #escaping (Result<Int, Error>) -> ()) {
debugPrint("Requested to create the user... \(user)")
complete(.failure(toThrow.ACCOUNT_ERROR))
}
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.
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
So I love declaring variables to hold return value and then return said variable on next line, thus making it easy to debug my code, I can just set a breakpoint at the return line and see what value it returns. I use this everywhere and it makes all my code so much easier to debug.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let cellCount = models.count
return cellCount
}
But then you have the scenario where you have optionals and different conditions that have to be met in order for your method to makes sense. The guard statement is great for making sure some conditions are met, while not introducing pyramids of doom.
But the problem with early returns is that you get at least two exit points (because guard requires a return in this context) from your method, which makes it harder to debug.
// Instantiate using dependency injection
private let reachability: ReachabilityProtocol
private let apiClient: APIClientProtocol
// Returns true if could start login request, else false
func loginUser(username: String, password: String) -> Bool {
defer {
// Not possible, would be nice! Return value would be an implicitly declared variable
// exaclty like the variables 'newValue' and 'oldValue' in property observers!
print("return value: \(returnValue)")
}
guard reachability.isOnline && !username.isEmpty && !password.isEmpty { return false }
apiClient.loginUser(username, password: password)
return true
}
It would be awesome if Swift 3.X would make the defer statement able to catch the return value, wouldn't it?
This would make debugging so much easier, whilst still making use of guard and early returns. I have not insight what so ever in writing compilers etc, but it feels like this would not be so hard to implement in coming versions of Swift?
Can you come up with a different way of achieving a single point to read return value of a method with multiple exit points? (Without having to wait for my suggested improvement to defer?)
Edit:
My example above with the login is not a perfect example, sorry, why would I write code like that? Haha! But there are many other similar scenarios, maybe something like this, using do-try-catch also makes code hard to debug:
// We don't know the return value of this function! Makes it hard to debug!
func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
defer {
// Not possible, would be nice! Return value would be an implicitly declared variable
// exaclty like the variables 'newValue' and 'oldValue' in property observers!
print("return value: \(returnValue)")
}
guard !firstName.isEmpty else { print("firstName can't be empty"); return nil }
guard !lastName.isEmpty else { print("lastName can't be empty"); return nil }
// Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
guard firstName != "DEBUG" else { return User.debugUser }
let fetchRequest = NSFetchRequest(entityName: Users.entityName)
let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
fetchRequest.predicate = predicate
do {
let user = try context.executeFetchRequest(fetchRequest)
return user
} catch let error as NSError {
print("Error fetching user: \(error)")
}
return nil
}
I like your suggested improvement to Swift to have defer capture the return value.
Here is something that will work, but it isn't perfect because it requires a little extra work (and code clutter), but you can do it manually by declaring returnValue with let at the top of your function, giving it the same type that the function returns. Then, replace all of your return <something> with returnValue = <something>; return returnValue.
By declaring returnValue with let, Swift will let you know if you forget to assign returnValue before leaving the function. So if you add a new return to your function, your function won't compile until you assign the returnValue. You'll see the error: error: constant 'returnValue' used before being initialized.
func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
let returnValue: User?
defer {
print("return value: \(returnValue)")
}
guard !firstName.isEmpty else { print("firstName can't be empty"); returnValue = nil; return returnValue }
guard !lastName.isEmpty else { print("lastName can't be empty"); returnValue = nil; return returnValue }
// Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
guard firstName != "DEBUG" else { returnValue = User.debugUser; return returnValue }
let fetchRequest = NSFetchRequest(entityName: Users.entityName)
let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
fetchRequest.predicate = predicate
do {
let user = try context.executeFetchRequest(fetchRequest)
returnValue = user; return returnValue
} catch let error as NSError {
print("Error fetching user: \(error)")
}
returnValue = nil; return returnValue
}
Alternatively (just brainstorming here...), put your function that has multiple exit points into an inner function, and then call it:
func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
func innerFunc(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
guard !firstName.isEmpty else { print("firstName can't be empty"); return nil }
guard !lastName.isEmpty else { print("lastName can't be empty"); return nil }
// Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
guard firstName != "DEBUG" else { return User.debugUser }
let fetchRequest = NSFetchRequest(entityName: Users.entityName)
let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
fetchRequest.predicate = predicate
do {
let user = try context.executeFetchRequest(fetchRequest)
return user.first as? User
} catch let error as NSError {
print("Error fetching user: \(error)")
}
return nil
}
let returnValue = innerFunc(firstName, andLastName: lastName, fromContext: context)
print("return value: \(returnValue)")
return returnValue
}