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

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

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

RealmとはSQLiteやCore Dataに代わるモバイルデータベースです。iOSとAndroidの両方でサポートされています。

Swift

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

Q&A

解決済

1回答

1942閲覧

realmでデータを保存すると、データが取得できなくなる

tirol_ts

総合スコア20

Realm

RealmとはSQLiteやCore Dataに代わるモバイルデータベースです。iOSとAndroidの両方でサポートされています。

Swift

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

0グッド

0クリップ

投稿2019/08/20 03:21

以下のようなGPXを読み込んで、Realmを用いて保存するプログラムを作成しました。ところが、データが変わってしまって、思うようにデータを取り出せません。RouteItem.route.map{$0.latitude}.max()!として座標の最大値を取りたいのですが...

print文の(1),(2)では正しく取得できるのですが、realm.add()以降は(3)で値が0.0となっており、(4)で取得できるようになっている状態です。(3)で取得できない理由と(2)で取得できる理由がよくわかりません。Realmと型についての知識が足りないのだと思いますが...

Swift

1class RouteItem: Object { 2 @objc dynamic var name = "" 3 @objc dynamic var secNum: Int = 0 4 var route = List<Point>() 5} 6 7class Point: Object { 8 @objc var longitude: Double = 0.0 9 @objc var latitude: Double = 0.0 10} 11 12class RouteManager: NSObject { 13 private var realm: Realm! 14 private var token: NotificationToken! 15 public private(set) var routeItems: Results<RouteItem>! 16 17 override init() { 18 super.init() 19 do { 20 realm = try Realm() 21 routeItems = realm.objects(RouteItem.self).sorted(byKeyPath: "name") 22 token = routeItems.observe { [weak self] _ in 23 NotificationCenter.default.post(name: .changedRouteItems, object: nil) 24 } 25 } catch { 26 print(error) 27 } 28 } 29 30 func createRoute(gpxURL: URL, routeName:String, sectionNum:Int) { 31  do { 32   let realm = try Realm() 33 34   let gpxreader = GPXReader() 35   let points = gpxreader.read(GPXUrl: gpxURL) // GPX(XMLを読み込んで、List<Point>()を返す 36   let routeItem = RouteItem() 37 38   routeItem.name = routeName 39   routeItem.secNum = sectionNum 40   routeItem.route = points 41   try! realm.write { 42 print("createFunc(1)",routeItem.route[0].latitude) 43 print("createFunc(2)",routeItem.route[0]["latitude"] 44 realm.add(routeItem) 45 print("createFunc(3)",routeItem.route[0].latitude) 46 print("createFunc(4)",routeItem.route[0]["latitude"] 47   } 48   } catch { 49   print(error) 50  } 51 } 52}

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

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

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

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

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

guest

回答1

0

ベストアンサー

(2)(4)で取得できて、(3)で取得できない理由は公式文書で記載箇所を確認した訳ではないですが、
realm.addされると、マネージドオブジェクト(realm側で管理するオブジェクト)になり、
プロパティではなく、辞書型で管理されるってことだと思います。
なので、管理前(realm.add前)はrouteItem.route[0].latitude(プロパティ)とかけていたのが、
途端に書けなくなり、辞書型で管理され、routeItem.route[0]"latitude"で表示となるようです。

しかし、保存前にletで用意したrouteItemをそのまま使い続けることは通常ないので、
そこを疑問に感じて解決しなくても、登場することはないと思います。

RealmのListは使い勝手が少し悪い印象で、
SQLなどと違い、RealmObjectのList以下を条件検索とか並び替えが簡単にできないからです。

今回の場合は、RouteItemとList<Point>()のPoint**"s"**に共通IDを持たせて、Listを使わず管理し、
それを元にRealm側で検索できるようにする方が便利なのではないかなって思います。

目的の最大値の取得ですが、

swift

1let maxLatitude = realm.objects(Point.self).filter("id == '******'").sorted(byKeyPath: "latitude").first

などで取得できると思います。

ちなみに順番はどうするんだという問題に関しては、gpsに記録されているDateを追加し、
登録された順番を持たせるといいのではないでしょうか?

RouteItemの名前毎もそうですが、日付順にデータを並び替えたり、取り出せた方がいいですよね?

追伸①、
let realm = Realm()は何度も登場するものですし、
privateなメンバー変数にしておいた方が便利ですよ。
private var realm: Realm!と入れ替えを。

追伸②、
RealmObjectに値を代入する時、アンマネージドオブジェクトならできますが、
writeの中で行う方が安全ですよ。
ex) routeItem.route = points とか
なぜなら、マネージドオブジェクトの変更なども混ざってくると、
どこは外に書いてもOKなのか、
どこをwriteの中に書かないといけないのかわかりずらくなると思いますので。
全部writeの中に書くようにしておく方が安全だと思います。

追伸③
イメージがつきやすいようにサンプルを適当に作ってみました。
(一部RouteItem, PointsとなっているのをTrip, GPSPointと読み替えてください)

イメージ説明

swift

1class Trip: Object { 2 @objc dynamic var id: Int = 0 3 @objc dynamic var name: String = "" 4 @objc dynamic var start: Date = Date() 5 @objc dynamic var end: Date = Date() 6 @objc dynamic var description: String = "" 7} 8 9class GPSPoint: Object { 10 @objc dynamic var tripId: Int = 0 11 @objc dynamic var timestamp: Date = Date() 12 @objc dynamic var longitude: Double = 0.0 13 @objc dynamic var latitude: Double = 0.0 14}

例えば、タイ貧乏旅行(id=1)とすると、
タイ貧乏旅行でのGPSPointは全部tripId = 1で登録する。
MasterViewでタイ貧乏旅行を押したら、tripId=1をDetailViewにpushする。
DetailViewではtripId=1で検索をかけて表示をする
こういうのが一般的だと思います。
List<*****>を持たせると、無駄に肥大化したデータを扱わないといけなくなると思います。

GPXファイルの読み込みも、viewDidLoadや場合によっては違うViewで読み込み、Realmに保存し、
表示はTableViewもしくはMapViewで行うのが普通だと思うので、
別々の場所になると思いますよ。

投稿2019/08/20 05:14

編集2019/08/20 14:35
hameji

総合スコア1380

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

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

tirol_ts

2019/08/20 08:37 編集

回答ありがとうございます! 何度も読み返したり調べたりしてみたのですが、あまりよく理解できなかったので、追加で質問させていただきます。 "保存前にletで用意したrouteItemをそのまま使い続けることは通常ない"というのが何故でしょうか? 共通IDに関して RealmでPointsとRouteItemを別々に保存して、それらをIDで紐づけるということでしょうか? "便利"というのはPointsのPointが多くなった場合などに、例えばtableViewでRouteItemの一覧を表示したいだけなのに、その下の座標データまで引っ張ってくることになるから、動作が重くなるといったことを考慮してのことでしょうか? 追記: realmObjectでやるfilterと普通の(?)Objectでやるfilterが違うもので、ネストが1段階の(?)Listだったら、条件検索が行えるという意味ですか? (https://realm.io/docs/swift/latest#queries) ```Swift class Point: Object { @objc var longitude: Double = 0.0 @objc var latitude: Double = 0.0 @objc var latitude: Double = 0.0 @objc var date: Date = Date(timeIntervalSince1970: 0.0) } class Points: Object { @objc dynamic id: Int = 0 // これと @objc dynamic points: List<Point>! } class RouteItem: Object { @objc dynamic id: Int = 0 // これが一致? @objc dynamic name: String == "" @objc dynamic var secNum: Int = 0 @objc dynamic date: Date = Date(timeIntervalSince1970: 0.0) } ``` 追伸②について マネージドオブジェクトだけでなく、アンマネージドオブジェクトに対する代入もwriteの中の方が安全というのは、どのような危険性が存在するゆえなのでしょうか?
hameji

2019/08/20 12:47

通常、データの保存と表示は別viewで行うことの方が多いと思います。 なので、realm.add(Object)した後に、Objectをそのまま利用することは少ないと思います。 TableViewなどでの表示を考えると、同じViewだとしても、 保存する時は1個のデータ(Object)であるのに対して、 TableViewに表示するためには( Results<Object> )と配列で取得するので、 登録した1個のデータ(Object)を使ってTableViewで表示することはないと思います。 イメージわきますか?
tirol_ts

2019/08/20 18:26 編集

丁寧にわかりやすい図まで作ってくださって本当にありがとうございます...!! routeManager.routeItem"s"のrouteItemが実際にはRealmを通して自動で値が更新されているのに、勘違いしてrouteManager内に直接routeItemが保持されていると思って質問してました。なので、利用しないということは理解できました。 勘違いに気づいてまた疑問を持ったのですが、routeManagerはAppDelegateでインスタンス化されているので、複数のViewControllerからrouteManagerを通してrouteItemにもアクセスできます。ただ、var realm = try! realmとして、realm.objects(RouteItem.self)としても取得(変更も)可能ですよね...? Realmを使う場合はどちらからアクセスするのが一般的(妥当)ですか? RouteItemとPointsを分離させてみたら、プロパティでアクセスできました。どうやら、List<List<Object>>な深いネスト(?)の型がダメだったようです。 pointsはルートを表しているので、1つのrouteItemに複数のPointを紐づける必要があって、そうするとPointsの中身(図だとGPXPointsに相当するところ)にList<Points>をもつ方法しか思い浮かばないのですが、Listを使わずともかけるのでしょうか? gitlabに一応ソースコードを載せてあるのでリンクを貼っておきます。 (分離させた方は、ブランチ(#1)を切って変更しました。) ご指摘してくださった点でまだ直り切ってないところがあったらごめんなさい。。。 https://gitlab.com/tsupiano/gr-timer
hameji

2019/08/20 23:38

通常はラッパークラスを持たせる(routeManager等)ということは、 以後はラッパークラスを通して、やり取りをするということになります。 Realmの仕様変更で書き方が変わってしまった場合などがあると、 Realmで直接各Viewに書いている全部変更をかけないといけなくなるなどの不都合が出てくるからです。
tirol_ts

2019/08/21 03:35

なるほど、良く分かりました! 色々と親切に教えてくださってありがとうございます。 大変勉強になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問