57417


SwiftでUserDefaultに構造体のArrayを保存したく、  
以下のコードを書いたのですが、

[code]

    // コンテンツを保持する構造体
    struct ROContents : Codable {
        var idString_: String       // コンテンツを一意に識別するID文字列
        var titleString_: String    // タイトル文字列
        var readOutString_: String  // 読み上げる文字列
    
        // 規定イニシャライザ
        init() {
            idString_      = ""
            titleString_   = ""
            readOutString_ = ""
        }
    }
    
[/code]

・・・

[code]

    func save() -> Bool {
    
        let userDefaults: UserDefaults = UserDefaults.standard
    
        let data = contentsArray_.map { try? JSONEncoder.encode($0) }
    　　　　　・・・
    
        return true
    }
    
[/code]

以下のエラーが出てしまいます。  
'ROContents' is not convertible to 'JSONEncoder'  
ROContentsで何らかのプロトコルを実装しないといけないのでしょうか。

=== 追記 ===

ご指摘頂いた通りに以下のようにメソッドを修正しました。

[code]

    func save() -> Bool {
    
        let userDefaults: UserDefaults = UserDefaults.standard
    
        let encoder = JSONEncoder()
        do {
            let data = try encoder.encode(contentsArray_)
            userDefaults.set(data, forKey: "contentsArray")
        } catch {
            print(error)
            return false
        }
    
        userDefaults.set(contentsCounter_, forKey: "contentsCounter")
    
        return true
    }
    
    func load() -> Bool {
    
        let userDefaults: UserDefaults = UserDefaults.standard
    
        let decoder = JSONDecoder()
        do {
            let data = userDefaults.data(forKey: "contentsArray")
            contentsArray_ = try [decoder.decode(ROContents.self, from:　data!)]
        } catch {
            print(error)
            return false
        }
    
        contentsCounter_ = userDefaults.integer(forKey: "contentsCounter")
    
        return true
    }
    
[/code]

実行してみた所  
save()の方は問題なかったのですが、  
load()の方は、以下のエラーが発生してしまいます。

typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [],
debugDescription: "Expected to decode Dictionary but found an array instead.",
underlyingError: nil))

戻り値がDictionaryなのに、配列（Array）に入れようとしている。  
ということだと思うのですが、decodeのやり方が間違っているのでしょうか。


CC BY-SA 4.0
2019-08-17T15:00:30.283
57416
2019-08-19T13:17:05.317
2019-08-19T02:24:07.517
23788
23788
question
swift
SwiftでUserDefaultsに構造体のArrayを保存したい。


`contentsArray_`は`[ROContents]`型として宣言されているものとします。このように重要な情報は必ずご質問中に記載するようにしてください。

(識別子の末尾に`_`をつけているのはどう言う理由でしょう? Swiftのコードとしては滅多に見られないものなので、できればやめた方が良いでしょう。)

`JSONEncoder`クラスの使い方が誤っています。`encode(_:)`はクラスメソッドではなく、インスタンスメソッドなので、まずは`JSONEncoder`クラスのインスタンスを作成しなければいけません。

[code]

            let encoder = JSONEncoder() //<-`JSONEncoder`のインスタンスを作成する
            do {
                let data = try encoder.encode(contentsArray_)
                //...
                return true
            } catch {
                print(error)
                return false
            }
    
[/code]

(`JSONEncoder`の使い方が100%理解できていない状態で`try?`を使うことはお勧めできません。実行時エラーが発生した場合にエラー情報が捨てられてしまうので、原因究明が困難になります。)

出来上がった`data`をどう使うつもりなのかによっては、後続のコードも修正の必要があるかもしれません。ご質問中では省略されてしまっているので、具体的には書けませんが、`UserDefaults`に保存するのが目的ならば、大きな変更にはならないはずです。

**_=== 追記 ===_** 部分への回答

保存と読み込みは当然ながら対になっていないといけません。

私の回答に基づいて更新された`save()`のやっているのは、概ねこんな感じです。

[code]

    構造体のArray ([ROContents]型)
    　　↓JSONEncoder.encode(_:)で変換
    Data型
    　　↓UserDefaults.set(_:forKey:)で保存
    UserDefaults内のDataオブジェクト
    
[/code]

これの逆をやるのですから、こう言う風にしないといけません。

[code]

    UserDefaults内のDataオブジェクト
    　　↓①
    Data型
    　　↓②
    構造体のArray ([ROContents]型)
    
[/code]

このうち①の部分、「UserDefaults内のDataオブジェクト」から`Data`型として値を取り出す部分は、あなたのコード(`data(forKey:)`で取り出し)で間違いありません。

②の部分で何が間違っているかは、お分かりでしょうか。「`JSONEncoder.encode(_:)`で変換」の対になるメソッドとして`JSONDecoder.decode(_:from:)`を使っているのは良いのですが、変換対象の型の指定が間違っています。UserDefaultsに保存された`Data`の元は`Array<ROContents>`(=`[ROContents]`)型だったのですから、`[ROContents]`型を指定してやらないといけません。

この行

[code]

            contentsArray_ = try [decoder.decode(ROContents.self, from:　data!)]
    
[/code]

を

[code]

            contentsArray_ = try decoder.decode([ROContents].self, from:　data!)
    
[/code]

と書き換えてください。

`decode(_:from:)`メソッドの第一引数は「〜型に」を表しているのですから、その部分に「構造体のArray」を表す型を記述します。

そのままだと`data`がnilの時にクラッシュしてしまうので、私ならこんな感じに書くでしょうか。

[code]

        func load() -> Bool {
            let userDefaults = UserDefaults.standard
    
            let decoder = JSONDecoder()
            do {
                guard let data = userDefaults.data(forKey: "contentsArray") else {
                    print("contentsArray cannot be found in UserDefaults")
                    return false
                }
                contentsArray_ = try decoder.decode([ROContents].self, from: data)
            } catch {
                print(error)
                return false
            }
    
            contentsCounter_ = userDefaults.integer(forKey: "contentsCounter")
    
            return true
        }
    
[/code]

(ちょっとした不注意でアプリがクラッシュしてしまう`!`が出てこないことに注意してください。)

* * *

既にご存知かもしれませんが、`UserDefaults`と言うのは「設定」アプリにある各アプリの設定画面程度の量の設定データを保存することを意図したクラスです。ユーザがアプリを使い続けると`[ROContents]`に何百何千ものデータが溜まっていく、と言った場合には、データの保存先としては適切ではないので、念のため。


CC BY-SA 4.0
2019-08-17T16:44:45.130
57417
2019-08-19T13:17:05.317
2019-08-19T13:17:05.317
13972
13972
57416
answer


> contentsArray_は[ROContents]型として宣言されているものとします。  
>  このように重要な情報は必ずご質問中に記載するようにしてください。

申し訳ありません。省略してしまいました。  
以後、気をつけます。

> (識別子の末尾に_をつけているのはどう言う理由でしょう?  
>  Swiftのコードとしては滅多に見られないものなので、  
>  できればやめた方が良いでしょう。)

Objective-C時代からの名残で、メンバ変数には、  
末尾に”＿”を付けております。

ご指摘の通りに書き換えて試したところ、  
UserDefault に保存出来ました！  
ありがとうございます！

ただ、今度は、読み込みの方でエラーが出てしまいます。

[code]

    func load() -> Bool {  
        let userDefaults: UserDefaults = UserDefaults.standard          
        contentsArray_ = userDefaults.array(forKey: "contentsArray") as! [ROContents]  
    
        return true  
    }  
    
[/code]

こちらも変更する必要がありますでしょうか。  
宜しくお願いいたします。


CC BY-SA 4.0
2019-08-18T03:59:10.910
57423
2019-08-18T03:59:10.910
23788
57416
answer