網站首頁 編程語言 正文
使用屬性封裝器來完美創建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
原文鏈接:https://juejin.cn/post/7195463851335221285
相關推薦
- 2022-08-18 R語言使用cgdsr包獲取TCGA數據示例詳解_R語言
- 2023-02-17 golang基礎之waitgroup用法以及使用要點_Golang
- 2022-10-30 數據庫加密字段進行模糊查詢詳解_數據庫其它
- 2023-06-04 Pandas中MultiIndex選擇并提取任何行和列_python
- 2022-05-22 Python?IO文件管理的具體使用_python
- 2022-09-14 Android?Activity通用懸浮可拖拽View封裝的思路詳解_Android
- 2023-12-14 Excel如何把兩列互換
- 2023-01-12 Python讀取及保存mat文件的注意事項說明_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支