Use attribute wrappers to perfectly create UserDefaults wrapper
Imagine you have an app that wants to implement the automatic login function. You use UserDefaults to encapsulate the read and write logic about UserDefaults. You will use UserDefaults to keep track of the automatic login "On/Off" status and userName. You might encapsulate UserDefaults in the following way
struct AppData { private static let enableAutoLoginKey = "enable_auto_login_key" private static let usernameKey = "username_key" static var enableAutoLogin: Bool { get { return (forKey: enableAutoLoginKey) } set { (newValue, forKey: enableAutoLoginKey) } } static var username: String { get { return } set { (newValueds, forKey: usernameKey) } } }
Through the introduction of property wrappers in Swift 5.1, we can simplify the above code, as follows
struct AppData { @Storage(key: "enable_auto_login_key", defaultValue: false) static var enableAutoLogin: Bool @Storage(key: "username_key", defaultValue: "") static var username: String }
Is this perfect? Let's watch
What is an attribute wrapper?
Before we get into the detailed discussion, let's take a quick look at what an attribute encapsulator is. Basically, an attribute encapsulator is a common data structure that intercepts read and write access to attributes, allowing custom behavior to be added during read and write attributes.
Can be passed through keywords@propertyWrapper
To declare an attribute wrapper. You want to have a string type attribute, and the console outputs it whenever this attribute is read and write. You can create a name calledPrintable
The property wrapper is as follows:
@propertyWrapper struct Printable { private var value: String = "" var wrapperValue: String { get { print("get value:\(value)") return value } set { print("set value:\(newValue)") value = newValue } } }
Through the above code, we can see that attribute encapsulation and otherstruct
Same. However, when defining an attribute encapsulator, there must be awrapppedValue
。 wrapppedValue
get
set
A code block is where you intercept and perform what you want. In this example, a code for printing status is added to output the values of get and set
Next, let's take a look at how to use the Printable property wrapper
struct Company { @Printable static var name: String } = "Adidas"
It should be noted that how we use it@
Symbols to declare a "name" variable encapsulated with an attribute encapsulator. If you want to try to type the above code in Playground, you will see the following output:
Set Value: Adidas
Get Value: Adidas
What is a UserDefault Packer
After understanding what the property wrapper is and how it works, we are now ready to implement ourUserDefaults
Packeter. To sum up, our property wrapper needs to continuously track the "On/Off" status of automatic login and the username of the user. By using the concepts we discussed above, we can easilyPrintable
The attribute wrapper is converted into an attribute wrapper that reads and writes during a read and write operation.
import Foundation @propertyWrapper struct Storage { private let key: String private let defaultValue: String init(key: Stirng, defaultValue: String) { = key = defaultValue } var wrappedValue: String { get { return (forKey: key) ?? defaultValue } set { (newValue, forKey: key) } } }
Here we name our property wrapperStorage
. There are two attributes, one iskey
, one isdefaultValue
。key
Will beUserDefaults
keys for reading and writing, anddefaultValue
Then asUserDefaults
Return value when there is no value.
Storage
Once the property wrapper is ready, we can start implementing itUserDefaults
The package is now. To be straightforward, we just need to create aStorage
The 'username' variable encapsulated by the property wrapper. It should be noted here that you can passkey
anddefaultValue
To initializeStorage
。
struct AppData { @Storage(key: "username_key", defaultValue: "") static var username: String }
After everything is ready,UserDefaults
The package is ready to use
= "swift-senpai" print()
At the same time, let's addenableAutoLogin
Variables to oursUserDefaults
In the package
struct AppData { @Storage(key: "username_key", defaultValue: "") static var username: String @Storage(key: "enable_auto_login_key", defaultValue: false) static var username: Bool }
At this time, the following two errors will be reported:
Cannot convert value of type ‘Bool’ to expected argument type ‘String’
Property type 'Bool' does not match that of lthe 'WrappedValue' property of its wrapper type 'Storage'
This is because our package only supportsString
type. To resolve these two errors, we need to generalize our property wrapper
Generalize the property wrapper
We must change the property encapsulatorwrappedValue
The data type to perform generalization of the encapsulator willString
Change the type to genericT
. Furthermore, we must use the common method fromUserDefaults
Read to updatewrappedValue
get
Code block
@propertyWrapper struct Storage<T> { private let key: String private let defaultValue: T init(key: String, defaultValue: T) { = key = defaultValue } var wrappedValue: T { get { // Read value from UserDefaults return (forKey: key) as? T ?? defaultValue } set { // Set value to UserDefaults (newValue, forKey: key) } } }
OK, with the general attribute wrapper, ourUserDefaults
The encapsulator can store Bool-type data
// The UserDefaults wrapper struct AppData { @Storage(key: "username_key", defaultValue: "") static var username: String @Storage(key: "enable_auto_login_key", defaultValue: false) static var enableAutoLogin: Bool } = true print() // true
Store custom objects
The above operations are all used for basic data types. But what if we want to store custom objects? Next, let's take a look at how to makeUserDefaults
Supports storage of custom objects
The content here is very simple, we will store a custom object toUserDefaults
In order to achieve this goal, we must transform itStorage
Type of property wrapperT
, make it followCodable
protocol
Then, inwrappedValue``set
We will use the code blockJSONEncoder
Convert custom objects to Data and write them toUserDefaults
middle. At the same time,wrappedValue``get
In the code block, we will useJSONDecoder
Take fromUserDefaults
The data read in it is converted into the corresponding data type. as follows:
@propertyWrapper struct Storage<T: Codable> { private let key: String private let defaultValue: T init(key: String, defaultValue: T) { = key = defaultValue } var wrappedValue: T { get { // Read value from UserDefaults guard let data = (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(, from: data) return value ?? defaultValue } set { // Convert newValue to data let data = try? JSONEncoder().encode(newValue) // Set value to UserDefaults (data, forKey: key) } } }
To let everyone see how to use the updatedStorage
For attribute wrapper, let's take a look at the next example. Imagine that you need to store user information returned by the server after the user logs in successfully. First, a struct holding the user information returned by the server is needed. This struct must be followedCodable
Protocol, so that it can be converted into Data to store itUserDefaults
middle
struct User: Codable { var firstName: String var lastName: String var lastLogin: Date? }
Next, inUserDefaults
Declare a packagerUser
Object
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 }
nailed it,UserDefaults
The encapsulator can now store custom objects
let johnWick = User(firstName: "John", lastName: "Wick", lastLogin: Date()) // Set custom object to UserDefaults wrapper = johnWick print() // John print() // Wick print(!) // 2019-10-06 09:40:26 +0000
The above is the detailed explanation of the use of the iOS Data Persistence UserDefaults Package. For more information about iOS Data Persistence UserDefaults, please pay attention to my other related articles!