swift

構造体をUserDefaultsで読み書きする

まとめ
・構造体に含める変数名はキャメルケースにする
・構造体の変数には独自クラスを含めない
・"構造体の型" は、"JSONEncoder()" を使って "data型” に変換する
・"data型” は、"JSON Decoder()" を使って "構造体の型" に変換する

構造体(struct)をUserDefaultsで読み書きするとき、書き込みはできるのに読み出すと "nil" になるという事象がありました。エラーも発生せず、原因が特定できませんでした。今回は結果論(多分バグでは、、)ですが、対処方法を整理します。

値を永続化したい構造体(変数)を定義する

ここが本記事で一番重要です。AppStoreから取得する購入情報を格納するための構造体です。ぱっと見では特におかしいところは無いんです。でも、これでは読み出しができません。

struct PurchasesInfo {
    var productID: String
    var isUpadate: Bool
    var expirationDate: Date?
    var revocationDate: Date?
    
    init(productID: String, isUpadate: Bool, expirationDate: Date?, revocationDate: Date?) {
        self.productID = productID
        self.isUpadate = isUpadate
        self.expirationDate = expirationDate
        self.revocationDate = revocationDate
    }
}

こちらが正常に動作する構造体の定義です。どこが違うかわかりますか?

struct PurchasesInfo {
    var productId: String
    var isUpadate: Bool
    var expirationDate: Date?
    var revocationDate: Date?
    
    init(productId: String, isUpadate: Bool, expirationDate: Date?, revocationDate: Date?) {
        self.productId = productId
        self.isUpadate = isUpadate
        self.expirationDate = expirationDate
        self.revocationDate = revocationDate
    }
}

"ID"の部分が違います。

正:var productId: String
誤:var productID: String

ちなみに、変数名を変えてみた結果は次のとおり。

誤:var ID: String
誤:var Id: String
正:var productAe: String
正:var productAeC: String
誤:var productAeCC: String

そろそろお気づきだと思いますが、"ID"という文字列がキャメルケースになっていないからです。じゃあ、書き込みもエラーにしてくれればいいのに、、

構造体をdata型にエンコードしUserdefaultsに保存する

UserDefaultsは独自で定義した型の読み書きには対応していませんが、独自で定義した型を含まない構造体であれば、data型に変換することで扱うことができます(UserDefaults.standard.setで定義されていない型(Date型など)を扱う場合も同様です)。

Data型へのエンコードと、userdefaultへの書き込み処理をまとめると次のようになります。

func userdefaultsStandardSet(forKey: String, purchasesInfos: [PurchasesInfo]) -> Bool {
                print("get-- userdefaultsStandardSet")
                // `JSONEncoder` で `Data` 型へエンコードし、UserDefaultsに追加する
                let jsonEncoder = JSONEncoder()
                jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
                guard let data = try? jsonEncoder.encode(purchasesInfos) else {
                    return false
                }
                UserDefaults.standard.set(data, forKey: forKey)
                return true
            }

UserDefaultsから読み出したdata型を構造体に変換する

次は、読み出し処理です。書き込み時と逆の処理になるため、data型で読み出された値を、構造体に変換します。

func userdefaultsStandardPurchasesInfo(forKey: String) -> [PurchasesInfo]? {
                print("get-- userdefaultsStandardPurchasesInfo")
                // `JSONDecoder` で `Data` 型を自作した構造体へデコードする
                let jsonDecoder = JSONDecoder()
                jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
                guard let data = UserDefaults.standard.data(forKey: forKey),
                let date = try? jsonDecoder.decode([PurchasesInfo].self, from: data) else {
                    print("return nil")
                    return nil
                }
                print("return date.count:\(date.count)")
                return date
            }

変数定義にUserDafaultsへの読み書きを定義する

変数定義にUserdDefaultsへの読み書きをクロージャーで定義します。こうすることで、変数の値が変化するたびにUserDefaultsに書き込まれます。

    static var purchasedInfos: [PurchasesInfo] {
        get {
            print("get-- purchasedInfos")
            
            if let result = userdefaultsStandardPurchasesInfo(forKey: "purchasedInfos") {
                return result
            } else {
                print("return nil")
                return []
            }
        }
        set {
            print("set-- purchasedInfos:\(newValue)")
             
            let result = userdefaultsStandardSet(forKey: "purchasedInfos", purchasesInfos: newValue)
            print("set result:\(result)")
        }
    }

まとめ

まとめ
・構造体に含める変数名はキャメルケースにする
・構造体の変数には独自クラスを含めない
・"構造体の型" は、"JSONEncoder()" を使って "data型” に変換する
・"data型” は、"JSON Decoder()" を使って "構造体の型" に変換する

-swift
-,