Vincent Solutions Vincent Solutions - 1 month ago 12
Swift Question

KeychainWrapper saving data and retrieving it in the same function isn't working

So I have a custom function that makes a HTTP POST Request to my webserver to get some fields from my database and then returns it to my iOS App that will save that field value as a keychain item, using KeychainWrapper.

My iOS App is as simple as login screen that checks if user credentials are correct in the mySQL Database using HTTP POST again and if correct, brings me to a user page where it's supposed to display some information grabbed and displayed using KeychainWrapper.

Now the problem I'm facing is that whenever I press the login button on the login page and I land on the user page, all fields are displayed as null. But if I stop the build and re-build, it will load back to that page with the fields loaded this time.
As if when I get on the user page for the first time, the fields aren't saved in the keychain yet, but when I rebuild it, they are and they load from there.

I'll cut some parts out of my code to make it short and keep it relevant to this question. Here is my function that request the meta fields from the MYSQL Database :

func requestMetaField(metaField:String) -> String {

let userEmail: String = KeychainWrapper.standard.string(forKey: "email")!;
let userPassword: String = KeychainWrapper.standard.string(forKey: "password")!;
let meta = metaField;

// Send data to server side
// Classic HTTP POST Request using JSONSerialization here.

if(resultValue=="Success") {

// Save downloaded data to keychain
self.saveDataToKeychain(value: metaValue, key: meta);
}

// End of the HTTP POST Request and else statements.

if let metaValue: String = KeychainWrapper.standard.string(forKey: meta) {
return metaValue;
} else {
return "N/A";
}

}


Here is my saveDataToKeychain Function :

func saveDataToKeychain(value:String, key:String) -> Bool {
let saveSuccessful: Bool = KeychainWrapper.standard.set(value, forKey: key);
return saveSuccessful;
}


Here is where I use the function on the user page to apply it to the labels :

override func viewDidLoad() {
super.viewDidLoad()

logoutButton.layer.cornerRadius = 6;

let firstName = requestMetaField(metaField: "first_name");
let lastName = requestMetaField(metaField: "last_name");
userFullName.text = "\(firstName) \(lastName)";

let userMinuteBalance = requestMetaField(metaField: "mycred_default_total");
userBalance.text = "\(userMinuteBalance) min";
}


And here is where I save the email & password in keychain when I use the login button :

if(resultValue=="Success") {

// Login is successful
UserDefaults.standard.set(true, forKey: "isUserLoggedIn");
UserDefaults.standard.synchronize();

// Save email & password in keychain
self.saveDataToKeychain(value: userEmail!, key: "email");
self.saveDataToKeychain(value: userPassword!, key: "password");

isUserLoggedIn = true;
}


Does anyone have an idea on how I could access the data immediately as I login ?

Also, I used the Keychain method because I've read that it's one of the safest of user information, but I'm open to other options if someone think there is an easier way to do this than using the keychain.

Thanks a lot for any help you can give !!

Answer

I think the problem is that you are treating requestMetaField as a synchronous function, when it is asynchronous. Your HTTP request will not finish until some time after requestMetaField returns.

Another approach would be to write a version of requestMetaField that takes a callback function that is called when the request completes. Then you can update your UI from the callback.

Here's what such a requestMetaField would look like:

func requestMetaField(metaField:String, callback: @escaping (String) -> Void) {

        let userEmail: String = KeychainWrapper.standard.string(forKey: "email")!;
        let userPassword: String = KeychainWrapper.standard.string(forKey: "password")!;
        let meta = metaField;

        // Send data to server side
        // Classic HTTP POST Request using JSONSerialization here.

                    if(resultValue=="Success") {

                        // Save downloaded data to keychain
                        self.saveDataToKeychain(value: metaValue, key: meta);

                        // call callback with value
                        callback(metaValue)
                    }

The code in viewDidLoad would look like:

    requestMetaField(metaField: "first_name") { firstName in 
        requestMetaField(metaField: "last_name") { lastName in
            DispatchQueue.main.async {        
                userFullName.text = "\(firstName) \(lastName)"
            }
        }
    }

    requestMetaField(metaField: "mycred_default_total") { userMinuteBalance in
        DispatchQueue.main.async {        
            userBalance.text = "\(userMinuteBalance) min"
        }
    }
Comments