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

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

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

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Swift

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

Q&A

解決済

1回答

1033閲覧

【Firestore】取得したソート順序のまま、Reference型を使ってデータを再取得したい

aiiro_swift

総合スコア15

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

Swift

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

0グッド

0クリップ

投稿2019/04/12 13:20

編集2019/04/12 13:32

前提・実現したいこと

Firestoreで以下のようなデータ構造を作成しています。

CollectionA documentAA -- key:value value:A (String型) documentAB -- key:value value:B (String型) documentAC -- key:value value:C (String型) CollectionB documentBA -- key:ref value:/CollectionA/documentAC (reference型) -- key:date value:2019/4/12 HH:MM:SS (timestamp型) documentBB -- key:ref value:/CollectionA/documentAB (reference型) -- key:date value:2019/4/11 HH:MM:SS (timestamp型) documentBC -- key:ref value:/CollectionA/documentAA (reference型) -- key:date value:2019/4/10 HH:MM:SS (timestamp型)

CollectionBでdateの降順に取得し、
そこからCollectionA内のvalueを降順に合わせて出力したいのですが、うまくいっておりません。
こういった類はデータ構造から見直しを行う必要がありますでしょうか?
(なお、一括取得後にクライアント側でソートする方法以外を検討しています)

<出力例>
C
B
A

試したこと

以下のようなソースを実行しましたが、CBAの順序で出力されずにBCA等順序がバラバラになってしまいます。
どうやらcollectionBの取得時は降順になっているのですが、その後の再取得処理にて、順序がおかしくなっているようにみえます。

Swift

1 2let db = Firestore.firestore() 3let collectionBRef = db.collection("CollectionB").order(by:"date",descending: true) 4 5// ここでCollectionBをソートして取得 6collectionBref.getDocuments(){(querySnapshot,err) in 7  for document in querySnapshot!.documents { 8    let data = document.data() as NSDictionary 9    let ref:DocumentReference = data["ref"] as! DocumentReference 10 11    //ソートされた順で、随時CollecitonAの情報を取得 12    ref.getDocument(){(document, error) in 13      let data = document.data() as! NSDictionary 14      let str = data["value"] as! String 15      print(str) //この出力時には順序が保持されない 16    } 17  } 18} 19

よろしくお願いいたします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

順序がバラバラになっ (たように見え) てしまうのは、 getDocument() の呼び出しが非同期に処理される ためかと存じます。解決するには、クライアント側で順序を保持しておき、全ての document が取得された後に print() するようなコードを書く必要があるのではないでしょうか。

原因

Firebase SDK は Firestore への読み書きを行う際、その処理が非同期に行われるような設計になって居ります。態々取得結果への処理をクロージャで書いて getDocument() 等のメソッドに渡しているのもこのためですね。換言すれば、 getDocument() 等を 呼び出した段階では document の取得を "要求" するだけ であって、その時点で結果は得られないということです。そして実際の取得結果はその後 取得できたものから次々と (クロージャに) 渡される ため、必ずしも "要求" の順序と一致するとは限りません。

結果、御呈示のコードでは次のような順序で処理が行われ、 CollectionA の取得結果が順不同となってしまいます。

  1. CollectionB の document を date の降順で要求する
  2. 1 の要求に対する結果が返り、 date 順に並んだ document の集合が得られる
  3. 集合の各 CollectionB document に対して、 CollectionA への reference を用いて紐づく document を一つ一つ "要求" する (この時点では希望の順番通りになっている)
  4. 3 で行った複数の要求が並列処理され、データ取得に成功したものから次々に渡される (この時点で希望の順序は失われている)

解決策

getDocument() の結果が非同期に順不同に渡されるのは避けられませんので、その 順序を維持するには自身で何らかの工夫をする 必要があります。例えば、次のような方法があるのではないでしょうか。

  1. CollectionB の各 document が持つ reference だけを要素とした配列 references と、同じ長さの配列 resolvedDocuments を用意しておく
  2. references の全ての要素に対して getDocument() をするが、そのクロージャでは単に references 内で documentID が一致する reference と同じ index で resolvedDocuments に結果を保持しておくだけとする
  3. 但し、最後の document が得られたら (== 全ての document の取得が終わったら) 、改めて resolvedDocuments を引数にして print() なりなんなりの処理を行う別の関数を呼び出す処理としておく

データ構造の見直しについて

非正規化などでデータ構造を見直すべきか否かは、 各 collection の document がどのような性質のデータを持ち、どのように用いられるか次第 かと存じますので、この処理による順序性を維持する目的だけでは「こうすべき」「ああすべき」とは言えないように思います。

例えば CollectionA が本当に value フィールドしか持たないのであれば、それ自体を CollectionB の document に持たせてもいいかもしれません。あるいは他にも複数のフィールドがあったとしても、今回の御質問のような read がとにかく多く発生するのであれば、 reference ではなく object にして CollectionA の document そのものを含めてしまう非正規化を行うのも手でしょう (最初の getDocuments() の時点で必要なデータが全て揃うので、順序性を気にする必要がなくなります) 。但しこの場合、 CollectionA への更新をするときは各 CollectionB document に含めたすべてのコピーを更新する必要がありますから、更新が頻繁に生じる場合は不適なやり方と言えます。

何れにせよ、 Firestore 自体が 「必要なデータを都度取得する」 / 「クエリの機能を単純化し、複雑な処理が必要ならデータを取得してからクライアント側で行う」 思想で作られていることもあり、クライアント側で多少のデータ加工や工夫が必要な事自体は不自然なことでは (そして常に避けられることでは) 御座いませんから、それ自体だけがデータ構造見直しの理由にはならないという事です。

とはいえ、「データ量が少なく、性能やら何やらを気にするほどではないから、多少 write のコストが高くてもクライアント実装を簡単にするために非正規化する」という判断も十分あり得ますから、その辺りは御事情に合わせて御検討下さい。

余談

因みに、この「複数の getDocument() 呼び出しの順序を維持し、全ての結果が得られてから関数を呼ぶ」という処理は多くの人に需要があるようで、 Node.js 用の SDK では (恐らくは Promise という強力な仕組みが存在するおかげで) Firestore.getAll() という関数 として実装されていたりします。

ただ残念ながら、 iOS SDK では Feature Request は行われている ものの、優先度が低いとして実装されるには至って居りません。当面は上記のように自分で何とかするしかなさそうです。

投稿2019/04/19 09:43

argparse

総合スコア1017

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

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

aiiro_swift

2019/04/21 02:00

回答ありがとうございます!まだiOS SDKでは実装されてなかったんですね。 まずはクライアントサイドで処理するように変えたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問