iOS數(shù)據(jù)持久化UserDefaults封裝器使用詳解
使用屬性封裝器來完美創(chuàng)建UserDefaults封裝器
想象一下,你有一個應(yīng)用想實現(xiàn)自動登錄功能。你用UserDefaults封裝了關(guān)于UserDefaults的讀與寫邏輯。你會用UserDefaults封裝來保持對自動登錄”On/Off“狀態(tài)、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 }
這樣就很完美了嗎?接著看
什么是屬性封裝器?
在我們進入詳細討論之前,我們先快速地了解一下什么是屬性封裝器 基本上來講,屬性封裝器是一種通用數(shù)據(jù)結(jié)構(gòu),可以攔截屬性的讀寫訪問,從而允許在屬性的讀寫期間添加自定義行為。
可以通過關(guān)鍵字@propertyWrapper
來聲明一個屬性封裝器。你想要有一個字符串類型的屬性,每當(dāng)這個屬性被進行讀寫操作的時候,控制臺就會輸出。你可以創(chuàng)建一個名為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
一樣。然而,當(dāng)定義一個屬性封裝器的時候,必須要有一個wrapppedValue
。 wrapppedValue
get
set
代碼塊就是攔截和執(zhí)行你想要的操作的地方。在這個例子中,添加了打印狀態(tài)的代碼來輸出get和set的值
接下來,我們看看,如何使用Printable屬性封裝器
struct Company { @Printable static var name: String } Company.name = "Adidas" Company.name
需要注意的是,我們?nèi)绾问褂?code>@符號來聲明一個用屬性封裝器封裝的”name“變量。如果你想要在Playground中嘗試敲出上述代碼的話,你會看到以下輸出:
Set Value: Adidas
Get Value: Adidas
什么是UserDefault封裝器
在理解了什么是屬性封裝器以及它是如何工作的之后,我們現(xiàn)在開始準(zhǔn)備實現(xiàn)我們的UserDefaults
封裝器??偨Y(jié)一下,我們的屬性封裝器需要持續(xù)跟蹤自動登錄的”On/Off“狀態(tài)以及用戶的username。 通過使用我們上述討論的概念,我們可以很輕松的將Printable
屬性封裝器轉(zhuǎn)化為在讀寫操作期間進行讀寫的屬性封裝器。
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
屬性封裝器準(zhǔn)備就緒后,我們就可以開始實現(xiàn)UserDefaults
封裝器了。直截了當(dāng),我們只需要創(chuàng)建一個被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
的數(shù)據(jù)類型來進行封裝器的通用化處理,將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類型的數(shù)據(jù)了
// 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
存儲自定義對象
上面的操作都是用來基本數(shù)據(jù)類型的。但是如果我們想要存儲自定義對象呢?接下來我們一起看看,如何能讓UserDefaults
支持自定義對象的存儲
這里的內(nèi)容很簡單,我們將會存儲一個自定義對象到UserDefaults
中,為了達到這個目的,我們必須改造一下Storage
屬性封裝器的類型T
,使其遵循Codable
協(xié)議
然后,在wrappedValue``set
代碼塊中我們將使用JSONEncoder
把自定義對象轉(zhuǎn)化為Data,并將其寫入UserDefaults
中。同時,在wrappedValue``get
代碼塊中,我們將使用JSONDecoder
把從UserDefaults
中讀取的數(shù)據(jù)轉(zhuǎn)化成對應(yīng)的數(shù)據(jù)類型。 如下:
@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
屬性封裝器,我們來看一下接下來的例子。 想象一下,你需要存儲用戶登錄成功后服務(wù)端返回的用戶信息。首先,需要一個持有服務(wù)端返回的用戶信息的struct。這個struct必須遵循Codable
協(xié)議,以至于他能被轉(zhuǎn)化為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
封裝器現(xiàn)在可以存儲自定義對象了
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
以上就是iOS數(shù)據(jù)持久化UserDefaults封裝器使用詳解的詳細內(nèi)容,更多關(guān)于iOS數(shù)據(jù)持久化UserDefaults的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS程序開發(fā)中設(shè)置UITableView的全屏分隔線的方法(不畫線)
ableView是app開發(fā)中常用到的控件,功能很強大,多用于數(shù)據(jù)的顯示。下面給大家介紹設(shè)置UITableView的全屏分隔線的兩種方法2016-04-04iOS通過UIDocumentInteractionController實現(xiàn)應(yīng)用間傳文件
這篇文章主要為大家介紹了iOS通過UIDocumentInteractionController實現(xiàn)應(yīng)用間傳文件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01iOS遍歷集合(NSArray、NSDictionary、NSSet)的方法總結(jié)
這篇文章主要介紹了iOS集合遍歷(NSArray、NSDictionary、NSSet)的方法,文中給出了詳細的方法示例,并總結(jié)了各個方法的優(yōu)缺點來供大家學(xué)習(xí)參考,需要的朋友們下面來一起看看吧。2017-03-03IOS開發(fā)中使用UIFont設(shè)置字體及批量創(chuàng)建控件
這篇文章主要介紹了IOS開發(fā)中使用UIFont設(shè)置字體及批量創(chuàng)建控件的方法,內(nèi)容很實用,感興趣的小伙伴們可以參考一下2016-03-03