运用特点封装器来完美创立UserDefaults封装器
想象一下,你有一个运用想完成主动登录功能。你用UserDefaults封装了关于UserDefaults的读与写逻辑。你会用UserDefaults封装来保持对主动登录”On/Off“状况、userName的跟踪。你可能会以下面这种方法来封装UserDefaults
struct AppData {
private static let enableAutoLoginKey = "enable_auto_login_key"
private static let usernameKey = "username_key"
static var enableAutoLogin: Bool {
get {
return UserDefaults.standard.bool(forKey: enableAutoLoginKey)
}
set {
UserDefaults.standard.set(newValue, forKey: enableAutoLoginKey)
}
}
static var username: String {
get {
return UserDefaults.standard.string
}
set {
UserDefaults.standard.set(newValueds, forKey: usernameKey)
}
}
}
经过Swift5.1关于特点封装器的介绍,咱们能够对上面的代码进行精简,如下
struct AppData {
@Storage(key: "enable_auto_login_key", defaultValue: false)
static var enableAutoLogin: Bool
@Storage(key: "username_key", defaultValue: "")
static var username: String
}
这样就很完美了吗?接着看
什么是特点封装器?
在咱们进入详细讨论之前,咱们先快速地了解一下什么是特点封装器
基本上来讲,特点封装器是一种通用数据结构,能够阻拦特点的读写访问,从而答应在特点的读写期间增加自界说行为。
能够经过关键字@propertyWrapper
来声明一个特点封装器。你想要有一个字符串类型的特点,每当这个特点被进行读写操作的时分,控制台就会输出。你能够创立一个名为Printable
的特点封装器,如下:
@propertyWrapper
struct Printable {
private var value: String = ""
var wrapperValue: String {
get {
print("get value:\(value)")
return value
}
set {
print("set value:\(newValue)")
value = newValue
}
}
}
经过上述代码咱们能够看出,特点封装跟其他struct
相同。但是,当界说一个特点封装器的时分,有必要要有一个wrapppedValue
。 wrapppedValue
get
set
代码块便是阻拦和执行你想要的操作的当地。在这个比如中,增加了打印状况的代码来输出get和set的值
接下来,咱们看看,怎么运用Printable特点封装器
struct Company {
@Printable static var name: String
}
Company.name = "Adidas"
Company.name
需要留意的是,咱们怎么运用@
符号来声明一个用特点封装器封装的”name“变量。如果你想要在Playground中尝试敲出上述代码的话,你会看到以下输出:
Set Value: Adidas
Get Value: Adidas
什么是UserDefault封装器
在理解了什么是特点封装器以及它是怎么工作的之后,咱们现在开始准备完成咱们的UserDefaults
封装器。总结一下,咱们的特点封装器需要持续跟踪主动登录的”On/Off“状况以及用户的username。
经过运用咱们上述讨论的概念,咱们能够很轻松的将Printable
特点封装器转化为在读写操作期间进行读写的特点封装器。
import Foundation
@propertyWrapper
struct Storage {
private let key: String
private let defaultValue: String
init(key: Stirng, defaultValue: String) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: String {
get {
return UserDefaults.standard.string(forKey: key) ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
在这儿,咱们将咱们的特点封装器命名为Storage
。有两个特点,一个是key
,一个是defaultValue
。key
将作为UserDefaults
读写时的键,而defaultValue
则作为UserDefaults
无值时分的回来值。
Storage
特点封装器准备就绪后,咱们就能够开始完成UserDefaults
封装器了。直截了当,咱们只需要创立一个被Storage
特点封装器封装的‘username’变量。这儿要留意的是,你能够经过key
和defaultValue
来初始化Storage
。
struct AppData {
@Storage(key: "username_key", defaultValue: "")
static var username: String
}
一切就绪之后,UserDefaults
封装器就能够运用了
AppData.username = "swift-senpai"
print(AppData.username)
同时,咱们来增加enableAutoLogin
变量到咱们的UserDefaults
封装器中
struct AppData {
@Storage(key: "username_key", defaultValue: "")
static var username: String
@Storage(key: "enable_auto_login_key", defaultValue: false)
static var username: Bool
}
这个时分,会报下面两种错误:
- 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’
这是由于咱们的封装器目前只支持String
类型。想要解决这两个错误,咱们需要将咱们的特点封装器进行通用化处理
将特点封装器进行通用化处理
咱们有必要改变特点封装器的wrappedValue
的数据类型来进行封装器的通用化处理,将String
类型改成泛型T
。从而,咱们有必要运用通用方法从UserDefaults
读取来更新wrappedValue
get
代码块
@propertyWrapper
struct Storage<T> {
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
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
// Set value to UserDefaults
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
好,有了通用特点封装器之后,咱们的UserDefaults
封装器就能够存储Bool类型的数据了
// 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
}
AppData.enableAutoLogin = true
print(AppData.enableAutoLogin) // true
存储自界说目标
上面的操作都是用来基本数据类型的。但是如果咱们想要存储自界说目标呢?接下来咱们一起看看,怎么能让UserDefaults
支持自界说目标的存储
这儿的内容很简单,咱们将会存储一个自界说目标到UserDefaults
中,为了达到这个目的,咱们有必要改造一下Storage
特点封装器的类型T
,使其遵循Codable
协议
然后,在wrappedValue``set
代码块中咱们将运用JSONEncoder
把自界说目标转化为Data,并将其写入UserDefaults
中。同时,在wrappedValue``get
代码块中,咱们将运用JSONDecoder
把从UserDefaults
中读取的数据转化成对应的数据类型。
如下:
@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)
}
}
}
为了让咱们看到怎么运用更新后的Storage
特点封装器,咱们来看一下接下来的比如。
想象一下,你需要存储用户登录成功后服务端回来的用户信息。首先,需要一个持有服务端回来的用户信息的struct。这个struct有必要遵循Codable
协议,以至于他能被转化为Data存储到UserDefaults
中
struct User: Codable {
var firstName: String
var lastName: String
var lastLogin: Date?
}
接下来,在UserDefaults
封装器中声明一个User
目标
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
}
搞定了,UserDefaults
封装器现在能够存储自界说目标了
let johnWick = User(firstName: "John", lastName: "Wick", lastLogin: Date())
// Set custom object to UserDefaults wrapper
AppData.user = johnWick
print(AppData.user.firstName) // John
print(AppData.user.lastName) // Wick
print(AppData.user.lastLogin!) // 2019-10-06 09:40:26 +0000
感谢咱们的阅读,给个赞呗