質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

4520閲覧

NSMutableArrayに格納した独自クラスごと、シリアライズしたい

bizkit.kit

総合スコア30

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

1グッド

2クリップ

投稿2016/09/17 16:07

独自クラスを作成して、それをNSMutableArrayに格納しているのですが、このNSMutableArray型の変数をシリアライズして、UserDefaultsに保存したいです。(Swift3でコーディングしています)

独自クラスは以下の様に定義しました。

swift

1class DisplayNutritionData :NSObject, NSCoding{ 2 3 // 栄養素名 4 var name :String = "" 5 6 // 表示するかどうか 7 var display :Bool = true 8 9 // 表示内容 10 var value :String = "" 11 12 init(name :String){ 13 self.name = name 14 } 15 16 func encode(with aCoder: NSCoder) { 17 aCoder.encode(name, forKey: "name") 18 aCoder.encode(display, forKey: "display") 19 aCoder.encode(value, forKey: "value") 20 } 21 22 required init?(coder aDecoder: NSCoder) { 23 name = aDecoder.decodeObject(forKey: "name") as! String 24 display = aDecoder.decodeBool(forKey: "display") 25 value = aDecoder.decodeObject(forKey: "value") as! String 26 } 27}

保存処理は次の様に書きました。

swift

1// itemsがNSMutableArray型の変数で、DisplayNutritionDataのインスタンスをいくつか追加してあります。 2let userDefault = UserDefaults.standard 3 let data :Data = NSKeyedArchiver.archivedData(withRootObject: items) as Data 4 userDefault.set(data, forKey: "displayNutrition") 5 userDefault.synchronize()

このコードだと、NSKeyedArchiver.archivedDataを呼んだ時点で落ちてしまいます。

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x600000052840'

Arrayに格納してある独自クラスにNSCodingのプロトコルを実装しておけばそれが呼ばれるのだと思ったのですがうまくいきませんでした。

Swift3でコーディングしているのですが、まだネットを探してもあまり情報がなく、困っています。
※Swift3というよりももっと根本的な問題かも知れませんが。。。

以上、よろしくお願い致します。

todayske👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/09/18 05:26

このコードで問題なく`UserDefaults`に保存、復元できましたよ。まっさらなプロジェクトで確かめてみたらどうですか?
bizkit.kit

2016/09/18 15:57

実際に試していただけたのですね。ありがとうございます。新しいプロジェクトで試してみましたが、結果は同じでした。。。
guest

回答2

0

func encode 内にブレークポイントは設定してみましたでしょうか。
何をエンコードしようとした時に落ちるのかが分かれば手がかりになるかもしれません。

Objective-Cだと items に格納されているモノが想定と違っていた・・・なんてことがすごくありがちですが
Swiftでもあり得なくはないですね。あと、SwiftだとOptional値が紛れ込んだりするとおかしくなることがありますね。

投稿2016/09/18 13:11

YokemuraTakeshi

総合スコア297

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bizkit.kit

2016/09/18 16:04

デバッグログにインスタンスのアドレスの様なものは表示されているのですが、実際にどのインスタンスをエンコードしようとして例外が発生しているのかがわかりませんでした。確かにその方法なら何かわかりそうですね。 itemsに入っているものが違うというのは、ジェネリクスとかそんな(すいません、Javaだとジェネリクスというのですが、Swiftでなんというのかわかりません)話でしょうか。 アドバイスありがとうございました。
YokemuraTakeshi

2016/09/18 22:51

配列の型が [Any] とか [AnyObject] だと、何でも入れられちゃうので必ずしも DisplayNutritionData のインスタンスが入っているかどうかわからない、ということですね。 Swiftで怪しいのは、Optional型が入っている可能性ですね。 Optional型はNSKeyedArchiverでシリアライズできない仕様なんですよ。 つまり、DisplayNutritionData でなく、DisplayNutritionData? (=Optional<DisplayNutritionData>)が入ってるとシリアライズできないです。 NSKeyedArchiver.archivedData の前にブレークポイントを置いて、デバッガコマンドで po items とやると、何が出てきますかね。 もし、Optional<...> みたいなモノが配列の中に入ってるような雰囲気でしたらそれが怪しいです。
bizkit.kit

2016/09/19 04:06

ありがとうございます。 (lldb) po items ▿ Optional<NSMutableArray> ▿ some : 47 elements ▿ 0 : <SerializeTest.DisplayNutritionData: 0x600000094730> ▿ 1 : <SerializeTest.DisplayNutritionData: 0x600000094000> ▿ 2 : <SerializeTest.DisplayNutritionData: 0x6000000945a0> (つづく) こんな感じでした。自作クラスが問題なのか、そもそもシリアライズ処理の書き方が問題なのか、、、その辺切り分けないと本質はわからなさそうですね。まだ全然初心者(始めて一ヶ月くらい)なので、勉強しないといけないですね。
YokemuraTakeshi

2016/09/20 04:25

解決なさったようですが、そもそも items 自体が Optinal でラップされてるので、これ自身がエンコードできてなかったかもですね。if-let 文で囲ってアンラップしてからエンコード、で良かったかもしれません。
guest

0

ベストアンサー

以下のようにすると配列の形で保存できます。
※ Swift3

swift

1class DisplayNutritionData :NSObject, NSCoding { 2 3 // 栄養素名 4 var name :String = "" 5 6 // 表示するかどうか 7 var display :Bool = true 8 9 // 表示内容 10 var value :String = "" 11 12 init(name :String, display: Bool = true, value: String = "") { 13 self.name = name 14 self.display = display 15 self.value = value 16 } 17 18 func encode(with aCoder: NSCoder) { 19 aCoder.encode(name, forKey: "name") 20 aCoder.encode(display, forKey: "display") 21 aCoder.encode(value, forKey: "value") 22 } 23 24 required init?(coder aDecoder: NSCoder) { 25 name = aDecoder.decodeObject(forKey: "name") as! String 26 display = aDecoder.decodeBool(forKey: "display") 27 value = aDecoder.decodeObject(forKey: "value") as! String 28 } 29} 30 31 32extension UserDefaults { 33 var nutritionDataArray: [DisplayNutritionData] { 34 get{ 35 let rowData: NSData = self.object(forKey: "nutritionData") as? NSData ?? NSData() 36 let datas = NSKeyedUnarchiver.unarchiveObject(with: rowData as Data) as? [DisplayNutritionData] ?? [] 37 return datas 38 } 39 set(newDatas) { 40 let archive = NSKeyedArchiver.archivedData(withRootObject: newDatas) 41 self.set(archive, forKey: "nutritionData") 42 } 43 } 44} 45 46 47// 使い方 48 49let userDefault = UserDefaults.standard 50let nutritionData = DisplayNutritionData(name: "test", display: false, value: "value1") 51userDefault.nutritionDataArray = [nutritionData] 52userDefault.synchronize() 53 54 55let nutritionData1 = userDefault.nutritionDataArray 56print(nutritionData1[0].name, nutritionData1[0].display, nutritionData1[0].value) 57//=> test false value1

投稿2016/09/18 01:28

編集2016/09/18 01:30
_Kentarou

総合スコア8490

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bizkit.kit

2016/09/18 16:07

ありがとうございます。教えていただいた方法で実装できました。 ただ、イマイチ何をやっているのかよくわかっていません(コピペしたので(^_^;) extensionを使ってUserDefaultsを拡張している・・・ということくらいはわかるのですが。。。 実際、配列に格納されている独自クラスをシリアライズすることは割とよくありそうなのですが、その場合は普通はどういう風にするものなのでしょうか? #ご教示いただいた内容は結構テクニカルな感じの印象を受けました。 お手すきでしたら、少しその辺を教えていただけると幸いです。
_Kentarou

2016/09/19 23:51

> 配列に格納されている独自クラスをシリアライズすることは割とよくありそうなのですが、その場合は普通はどういう風にするものなのでしょうか データ量が多くなければ簡単に扱えてよいと思いますが、一般的にはDatabaseを使うと思います。 UserDefaultsは全て読みだして、全て書き込むので一部更新や検索して取得などができませんので自分はあまり使用しませんね。
bizkit.kit

2016/09/20 02:05

なるほど。 実は扱っているデータが幾つかあって、量が多くて検索が必要なモノはRealmを使ってみています。 中途半端な量(50項目くらい)のものはUserDefaultsでもいいかな、と思って試していた次第です。 回答ありがとうございました!助かりました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問