首頁 > 軟體

iOS資料持久化UserDefaults封裝器使用詳解

2023-02-05 14:03:22

使用屬性封裝器來完美建立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一樣。然而,當定義一個屬性封裝器的時候,必須要有一個wrapppedValuewrapppedValue 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,一個是defaultValuekey將作為UserDefaults讀寫時的鍵,而defaultValue則作為UserDefaults無值時候的返回值。

Storage屬性封裝器準備就緒後,我們就可以開始實現UserDefaults封裝器了。直截了當,我們只需要建立一個被Storage屬性封裝器封裝的‘username’變數。這裡要注意的是,你可以通過keydefaultValue來初始化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

以上就是iOS資料持久化UserDefaults封裝器使用詳解的詳細內容,更多關於iOS資料持久化UserDefaults的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com