74765


ボタンを押した際にMyClassというクラスをdatas(配列)に追加してそのdatasをUserDefaultsを使って保存したいのですが、デシリアライズに失敗してしまいます。  
どうすればよいでしょうか？

[code]

    import Foundation
    
    class MyClass: NSObject, NSSecureCoding {
        static var supportsSecureCoding: Bool = true
    
        var text: String?
    
        override init() {
        }
    
        // 読み込み
        required init?(coder decoder: NSCoder) {
            if let text = decoder.decodeObject(forKey: "data") as? String {
                self.text = text
            }
        }
    
        // 保存
        func encode(with coder: NSCoder) {
            coder.encode(self.text, forKey: "data")
        }
    }
    
    
    import UIKit
    
    class ViewController: UIViewController {    
        var datas = [MyClass]()
        
        let userDefaults = UserDefaults.standard
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            if let storedDatas = self.userDefaults.array(forKey: "data") as? [Data] {
                for storedData in storedDatas {
                    if let unarchiveToDoList = try? NSKeyedUnarchiver.unarchivedObject(ofClass: MyClass.self, from: storedData){
                        self.datas.append(unarchiveToDoList)
                        print("デシリアライズ成功")
                    }
                }
            } else {
                print("デシリアライズ失敗")
            }
        }
        
        @IBAction func tapButton() {
            let data = MyClass()
            data.text = "hello"
            self.datas.append(data)
            if let archiveData = try? NSKeyedArchiver.archivedData(withRootObject: self.datas, requiringSecureCoding: false) {
                self.userDefaults.setValue(archiveData, forKey: "data")
                self.userDefaults.synchronize()
                print("シリアライズ成功")
            } else {
                print("シリアライズ失敗")
            }
        }
    }
    
[/code]


CC BY-SA 4.0
2021-03-19T08:35:48.867
74763
2021-03-19T10:44:00.800
2021-03-19T09:35:52.127
3060
40856
question
swift
カスタムクラスの配列をUserDafaultsとシリアライズ/デシリアライズで保存したい場合の方法がわかりません


再現性のあるコードのようなので、コメントした内容についてはこちらで実行した結果で判断させていただきます。「うまく動かない」コードについては、可能な限りで最大限の情報をご質問内に含めるようにして下さい。

あなたが保存時(`tabButton()`の後半)でやっているのは、こんなことになります。

  1. `NSKeyedArchiver.archivedData`で、`MyClass`の配列を1個のデータ`archiveData`に変換

※この時点では、`archiveData`は「1個のデータ」(`Data`型)です。`Data`の配列(`[Data]`)ではありません。

  2. `userDefaults.setValue`でそのデータをキー名`"data"`で`UserDefaults`に保存

※繰り返しになりますが、`"data"`に保存されたのは「1個のデータ」です。

* * *

ところがあなたは、データの取り出しの時にまずこんなことをやっています。

  1. `userDefaults.array`でキー名`"data"`に保存された **配列** を取り出す

`UserDefaults`の`"data"`には、「1個のデータ」が保存されているのに、それを配列として取り出そうとしても、必ず失敗し、`nil`を返します。

保存の際に、「`MyClass`の配列を1個のデータ」に変換するのは、`NSKeyedArchiver.archivedData`で行ったのですから、復元の際に「1個のデータを`MyClass`に変換」するのはその逆で、`NSKeyedUnarchiver.unarchivedObject`で行います。

* * *

iOS 14以降で追加されたメソッドを使っていいのなら、こんな感じになるでしょう。

[code]

    class ViewController: UIViewController {
        var todoItems = [MyClass]()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            if let storedData = UserDefaults.standard.data(forKey: "data") {
                do {
                    if let unarchiveToDoList = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: MyClass.self, from: storedData) {
                        self.todoItems = unarchiveToDoList
                        print("デシリアライズ成功")
                    } else {
                        print("`data`がMyClassの配列になっていません")
                    }
                } catch {
                    print(error)
                    print("デシリアライズ失敗")
                }
            } else {
                print("UserDefaultsに`data`は存在しません")
            }
        }
    
        @IBAction func tapButton() {
            let todo = MyClass()
            todo.text = "hello"
            self.todoItems.append(todo)
            do {
                let archiveData = try NSKeyedArchiver.archivedData(withRootObject: self.todoItems, requiringSecureCoding: false)
                UserDefaults.standard.setValue(archiveData, forKey: "data")
                //UserDefaults.standard.synchronize()
                print("シリアライズ成功")
            } catch {
                print("シリアライズ失敗")
            }
        }
    }
    
[/code]

`unarchivedArrayOfObjects`を使いたくないならこんな感じです。

[code]

        override func viewDidLoad() {
            super.viewDidLoad()
            
            if let storedData = UserDefaults.standard.data(forKey: "data") {
                do {
                    if let unarchiveToDoList = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, MyClass.self], from: storedData) as? [MyClass] {
                        self.todoItems = unarchiveToDoList
                        print("デシリアライズ成功")
                    } else {
                        print("`data`がMyClassの配列になっていません")
                    }
                } catch {
                    print(error)
                    print("デシリアライズ失敗")
                }
            } else {
                print("UserDefaultsに`data`は存在しません")
            }
        }
    
[/code]

細かい部分を言い出すときりがないのですが、`try`を使う場合には、必ずdo-try-
catchの形で使うことを心がけた方が良いでしょう。(どんな時にどんなエラーが起こるのか、100%頭の中に入っている、というなら別ですが…。)


CC BY-SA 4.0
2021-03-19T10:44:00.800
74765
2021-03-19T10:44:00.800
13972
74763
answer