Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@LeeKahSeng
Last active February 6, 2024 10:38
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LeeKahSeng/20e0c3602d1141af3bcff45f1f02df10 to your computer and use it in GitHub Desktop.
Save LeeKahSeng/20e0c3602d1141af3bcff45f1f02df10 to your computer and use it in GitHub Desktop.
Create the Perfect UserDefaults Wrapper Using Property Wrapper (https://swiftsenpai.com/swift/create-the-perfect-userdefaults-wrapper-using-property-wrapper/)
struct User: Codable {
var firstName: String
var lastName: String
var lastLogin: Date?
}
@propertyWrapper
struct Storage<T: Codable> {
private let key: String
private let defaultValue: T
init(key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
// Read value from UserDefaults
guard let data = UserDefaults.standard.object(forKey: key) as? Data else {
// Return defaultValue when no data in UserDefaults
return defaultValue
}
// Convert data to the desire data type
let value = try? JSONDecoder().decode(T.self, from: data)
return value ?? defaultValue
}
set {
// Convert newValue to data
let data = try? JSONEncoder().encode(newValue)
// Set value to UserDefaults
UserDefaults.standard.set(data, forKey: key)
}
}
}
@propertyWrapper
struct EncryptedStringStorage {
private let key: String
init(key: String) {
self.key = key
}
var wrappedValue: String {
get {
// Get encrypted string from UserDefaults
return UserDefaults.standard.string(forKey: key) ?? ""
}
set {
// Encrypt newValue before set to UserDefaults
let encrypted = encrypt(value: newValue)
UserDefaults.standard.set(encrypted, forKey: key)
}
}
private func encrypt(value: String) -> String {
// Encryption logic here
return String(value.reversed())
}
}
struct AppData {
@Storage(key: "username_key", defaultValue: "")
static var username: String
@Storage(key: "enable_auto_login_key", defaultValue: false)
static var enableAutoLogin: Bool
// Declare a User object
@Storage(key: "user_key", defaultValue: User(firstName: "", lastName: "", lastLogin: nil))
static var user: User
@EncryptedStringStorage(key: "password_key")
static var password: String
}
AppData.username = "swift-senpai"
print(AppData.username) // swift-senpai
AppData.enableAutoLogin = true
print(AppData.enableAutoLogin) // true
let johnWick = User(firstName: "John", lastName: "Wick", lastLogin: Date())
AppData.user = johnWick
print(AppData.user.firstName) // John
print(AppData.user.lastName) // Wick
print(AppData.user.lastLogin!) // 2019-10-06 09:40:26 +0000
AppData.password = "password1234"
print(AppData.password) // 4321drowssap
@NasrullahKhan
Copy link

Hi. after restarting i'm losing the data because storage takes default value. what's the best approach to get the data after app restart by using your @propertyWrapper Storage

@LeeKahSeng
Copy link
Author

Data will be lost when you run the code in Xcode Playground. However, if you use it in an app, the data will be persisted.

@KalinRangelovRangelov
Copy link

Hi, very nice implementation.
My question is: How can you inject mock/spy UserDefaults for unit testing?

@NikKovIos
Copy link

And addition:

             if let newValue {
                let data = try? JSONEncoder().encode(newValue)
                UserDefaults.standard.set(data, forKey: key)
            } else {
                UserDefaults.standard.removeObject(forKey: key)
            }

If set nil - remove data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment