KeychainWrapper, сохраняющий данные и извлекающий их с помощью одной и той же функции, не работает

#ios #swift #keychain

#iOS #swift #брелок

Вопрос:

Итак, у меня есть пользовательская функция, которая отправляет HTTP POST-запрос на мой веб-сервер для получения некоторых полей из моей базы данных, а затем возвращает его в мое приложение iOS, которое сохранит значение этого поля как элемент KeychainWrapper, используя KeychainWrapper.

Мое приложение для iOS так же просто, как экран входа в систему, который проверяет правильность учетных данных пользователя в базе данных MySQL, снова используя HTTP POST, и, если они верны, выводит меня на страницу пользователя, где предполагается отобразить некоторую информацию, полученную и отображаемую с помощью KeychainWrapper.

Теперь проблема, с которой я сталкиваюсь, заключается в том, что всякий раз, когда я нажимаю кнопку входа на странице входа и попадаю на страницу пользователя, все поля отображаются как null. Но если я остановлю сборку и повторю сборку, она загрузится обратно на эту страницу с полями, загруженными на этот раз. Как будто, когда я впервые захожу на страницу пользователя, поля еще не сохранены в связке ключей, но когда я ее перестраиваю, они сохраняются и загружаются оттуда.

Я вырежу некоторые части из своего кода, чтобы сделать его коротким и сохранить его актуальность для этого вопроса. Вот моя функция, которая запрашивает мета-поля из базы данных MYSQL :

 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";
        }

    }
  

Вот моя функция saveDataToKeychain :

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

Здесь я использую функцию на странице пользователя, чтобы применить ее к ярлыкам :

 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";
    }
  

И здесь я сохраняю адрес электронной почты и пароль в keychain при использовании кнопки входа :

 if(resultValue=="Success") {

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

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

                        isUserLoggedIn = true;
}
  

У кого-нибудь есть идея о том, как я мог бы получить доступ к данным сразу после входа в систему?

Кроме того, я использовал метод Keychain, потому что я читал, что это один из самых безопасных способов получения пользовательской информации, но я открыт для других вариантов, если кто-то считает, что есть более простой способ сделать это, чем с помощью keychain.

Большое спасибо за любую помощь, которую вы можете оказать!!

Ответ №1:

Я думаю, проблема в том, что вы обрабатываете requestMetaField как синхронную функцию, когда она асинхронна. Ваш HTTP-запрос завершится только через некоторое время после requestMetaField возврата.

Другим подходом было бы написать версию requestMetaField , которая принимает функцию обратного вызова, вызываемую при завершении запроса. Затем вы можете обновить свой пользовательский интерфейс с помощью обратного вызова.

Вот как это requestMetaField выглядело бы:

 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)
                    }
  

Код в viewDidLoad будет выглядеть следующим образом:

     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"
        }
    }
  

Комментарии:

1. Я вижу, да, должно быть, это так, я вроде как новичок в разработке swift, возможно, я это пропустил. Я не очень знаком с функциями обратного вызова. Знаете ли вы какую-либо документацию или руководство, которые я мог бы использовать для завершения вашего «другого подхода»? Большое спасибо за ваш ответ!

2. За вычетом нескольких изменений, которые мне пришлось внести в соответствии с Xcode (не смог value ввести callback:(value: String) , это было именно то, что мне было нужно. Большое спасибо, что нашли время для написания этого!!!

3. Также поставил @escaping после callback: , если кто-нибудь окажется здесь, пытаясь заставить это работать!