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

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

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

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

Cloud Firestore

Cloud Firestore は、自動スケーリングと高性能を実現し、アプリケーション開発を簡素化するように構築された NoSQLドキュメントデータベースです。

Swift

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

Q&A

解決済

1回答

1941閲覧

2回目以降のfor文をFirebaseの非同期処理が終わった後に実行したい(並行処理をさせたくない)

Naoki_Pro

総合スコア23

Firebase

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

Cloud Firestore

Cloud Firestore は、自動スケーリングと高性能を実現し、アプリケーション開発を簡素化するように構築された NoSQLドキュメントデータベースです。

Swift

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

0グッド

0クリップ

投稿2020/03/09 05:53

編集2020/03/09 09:08

こんにちは。お世話になります。

前提

SwfitとCloud Firestoreを使って、ユーザーがお気に入りのレストランを登録し、それをGoogle Map上でシェアするアプリを開発しています。
Map上のマーカーのウィンドウをタップすると画面遷移し、レストランのレビューを見ることができるようにしています。レビュー画面では投稿したユーザーが誰かも分かるようにしています。

またCloud Firestoreにはルートコレクションが2つあります。
Storesコレクションのドキュメントを全て取得し、それをfor文で回しています。各ドキュメント内のuserIdの値を取得した後に、それと一致するUserコレクションのドキュメントを参照して、そのドキュメント内にあるuserNameとuserImageを取り出して、レビュー画面に投稿したユーザー名とアイコンを反映しています。

実現したいこと・起きている問題

レビュー画面に値を反映することはできているのですが、投稿したユーザーとは関係ないユーザーの値が反映されてしまうのでそれを解決したいです。

考えられる原因

アプリビルド時のログを見ると、下記のコードで示している箇所まではfor文で実行した通りに処理が走っているのですが、Firebaseとの通信(非同期処理?)後のクロージャがfor文とは関係ない順番で処理が実行されているためレストランを投稿したユーザーと関係無いユーザーの値がレビュー画面に反映させてしまっているのだと考えています。

下のシュミレータ画面とcloud firestoreの画像をみて頂ければ分かるかと思いますが、はっぱを投稿したユーザーは値が反映されているユーザーではありません。

シュミレータ画面

シュミレータ画面

アプリビルド時のログ画面

ログ画面

Cloud Firestoreの構造

Stores
Users

該当のソースコード

Firebaseと通信するモデルクラス↓

Swift

1import Firebase 2 3class GetStoreInfo { 4 5 // MARK: - Properties 6 let db = Firestore.firestore() 7 8 // MARK: - Methods 9 func getStoreInfo(completion: @escaping (_ snapShot: QuerySnapshot) -> Void) { 10 let collRef = db.collection("Stores") 11 collRef.getDocuments { [weak self] (snapShot, error) in 12 if let _ = self { 13 14 if error != nil { 15 print("could not get store data..") 16 } else { 17 guard let unwrappedSnapShot = snapShot else { return } 18 completion(unwrappedSnapShot) 19 } 20 21 } 22 23 } 24 } 25 26 func getPostUserInfo(userId: String, storeName: String, storeReview: String ,completion: @escaping (_ snapShot: DocumentSnapshot) -> Void) { 27 print("getPostUserInfoメソッドだよ") 28 print(userId) 29 let docRef = db.collection("Users").document(userId) 30 // ここまではfor文で順番通りに動く⬆️ 31 32 // ここからfor文での順番通りに動かなくなり順番が狂う⬇️ 33 docRef.getDocument { [weak self] (snapShot, error) in 34 if let _ = self { 35 36 print("クロージャに入ったよ") 37 guard let unwrappedSnapShot = snapShot else { return } 38 print("アンラップしたよ") 39 completion(unwrappedSnapShot) 40 41 } 42 } 43 } 44}

ViewController↓

Swift

1import UIKit 2import GoogleMaps 3 4class MapViewController: UIViewController { 5 6 // MARK: - Properties 7 var mapView: MapView! 8 var delegate: MapViewControllerDelegate? 9 var storeModel: GetStoreInfo! 10 var storeDetailViewControllers: [StoreDetailViewController] = [] 11 var storeName: [String] = [] 12 var storeReview: [String] = [] 13 var storeImage: [String] = [] 14 var count = 0 15 var storeCount = 0 16 17 // MARK: - Helper Functions 18 override func viewDidLoad() { 19 super.viewDidLoad() 20 21 storeDetailViewControllers.removeAll() 22 storeName.removeAll() 23 storeReview.removeAll() 24 storeName.removeAll() 25 postUserNameArray.removeAll() 26 postUserIconArray.removeAll() 27     getStoresData() 28 } 29 30 func getStoresData() { 31 32 print("start get stores data..") 33 storeModel = GetStoreInfo() 34 storeModel.getStoreInfo { [weak self] (snapShot) in 35 if let self = self { 36 // for文でドキュメントを順番に取得 37 for document in snapShot.documents { 38 let storeData = document.data() 39 guard let latitude = storeData["latitude"] as? Double, let longitude = storeData["longitude"] as? Double, let storeName = storeData["storeName"] as? String, let storeImage = storeData["storeImage"] as? String, let storeReview = storeData["storeImpression"] as? String, let userId = storeData["userId"] as? String else { return } 40 41 42 self.configureMakerInMap(latitude: latitude, longitude: longitude, storeName: storeName, count: self.count, storeReview: storeReview, storeImage: storeImage, userId: userId) 43 self.count += 1 44 } 45 } 46 47 48 } 49 50 } 51 52 func configureMakerInMap(latitude: Double, longitude: Double, storeName: String, count: Int, storeReview: String, storeImage: String, userId: String) { 53 54 print("configure maker..") 55 let position = CLLocationCoordinate2DMake(latitude, longitude) 56 let marker = GMSMarker(position: position) 57 marker.title = storeName 58     // markerの識別子 59 marker.identifier = count 60 marker.map = mapView.mapView 61 // for文で順番に取得した値を配列に入れる 62 self.storeName.append(storeName) 63 self.storeReview.append(storeReview) 64 self.storeImage.append(storeImage) 65 print(userId) 66 self.storeModel.getPostUserInfo(userId: userId, storeName: storeName, storeReview: storeReview) { [weak self] (snapShot) in 67 if let self = self { 68         // クロージャから出てきたこの場所で順番がおかしくなる 69 let userInfo = snapShot.data() 70 print("0") 71 print("出てきたよ") 72 73 guard let postUserName = userInfo!["userName"] as? String, let postUserIcon = userInfo!["userImage"] as? String else { return } 74 print(postUserName) 75 print(postUserIcon) 76 77         // 取得したレストランの情報とユーザーの情報を、引数で渡してレビュー画面を作成 78 self.configureViewController(storeName: self.storeName[self.storeCount], storeReview: self.storeReview[self.storeCount], storeImage: self.storeImage[self.storeCount], count: self.count, postUserName: postUserName, postUserIcon: postUserIcon) 79 self.storeCount += 1 80 } 81 } 82 83 84 } 85 86 func configureViewController(storeName: String, storeReview: String, storeImage: String, count: Int, postUserName: String, postUserIcon: String) { 87 88 storeDetailViewControllers.append(StoreDetailViewController(storeName: storeName, storeReview: storeReview, storeImage: storeImage, postUserName: postUserName, postUserIcon: postUserIcon,count: count)) 89 90 } 91 92} 93 94// Markerに識別子を持たせるための変数 95extension GMSMarker { 96 var identifier: Int { 97 set(identifier) { 98 self.userData = identifier 99 } 100 101 get { 102 return self.userData as! Int 103 } 104 } 105} 106 107// MARK: Delegate 108extension MapViewController: GMSMapViewDelegate { 109 110 func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker) { 111 print(marker.identifier) 112 let navStoreDetailViewController = UINavigationController(rootViewController: storeDetailViewControllers[marker.identifier]) 113 navStoreDetailViewController.modalPresentationStyle = .fullScreen 114 present(navStoreDetailViewController, animated: true, completion: nil) 115 print("did tap info window of maker..") 116 } 117 118} 119

試したこと

クロージャについて調べていたら、weak selfというのが出てきたのでコードを書いてみたのですが恐らく関係ありませんでした。
Firebaseとの非同期通信まで終わった後に、次のfor文内の処理が動くようにすれば解決できると思うのですが、調べても出てきませんでした。(そもそもそんなやり方が無いのかもしれませんが)

また、Firebase登録されているレストランを一つだけにしてみたらfor文は一度しか動かないので、投稿したユーザーは一致しました。逆にFirebase登録されているレストランを増やすほど、投稿したユーザーとは関係ないユーザーがばらばらに表示されてしまいます。

説明が分かりにくかったらすみません。
解決方法が分かる方がいましたら、ご回答お願いします。
また別の方法でやりたいことが実装できるのであれば、その方法を教えて頂きたいです。

追記
調べていたらforEach文というのが出てきたので、それを試してみましたがダメでした。
参考にしたサイト

補足情報(FW/ツールのバージョン)

xcode 11.3.1

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

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

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

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

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

guest

回答1

0

ベストアンサー

あらかじめ getPostUserInfo するという前提だと、どうしても結果は非同期になりますから、結果の格納方法を工夫することになると思います。つまり、storeDetailViewControllers を辞書にして、

swift

1class MapViewController: UIViewController { 2 3 var storeDetailViewControllers: [Int: StoreDetailViewController] = [:]

swift

1 func configureViewController(storeName: String, storeReview: String, storeImage: String, count: Int, postUserName: String, postUserIcon: String) { 2 3 storeDetailViewControllers[count] = StoreDetailViewController(storeName: storeName, storeReview: storeReview, storeImage: storeImage, postUserName: postUserName, postUserIcon: postUserIcon,count: count) 4 5 }

みたいな。ただし、StoreDetailViewController をあらかじめたくさん作るのはあまり良くないので、画面遷移時に getPostUserInfo して、結果を受け取ってから画面遷移する(または、画面遷移後に getPostUserInfo する)という手もあります。
あと、configureViewController の引数 storeName, storeReview, storeImage, count, postUserName, postUserIcon ですが、まとめて構造体かオブジェクトにした方がいろいろ便利になると思います。

投稿2020/03/09 10:51

hoshi-takanori

総合スコア7895

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

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

Naoki_Pro

2020/03/09 12:48

いつもありがとうございます。 ご回答の 「どうしても結果は非同期になりますから、結果の格納方法を工夫することになると思います。」 というのは for in 文 で実行した通りの順番でFirebaseから値を取り出すのは無理ということなのでしょうか? 辞書型の使い方が慣れていないので、上手くできるか分かりませんがやってみますm(__)m
Naoki_Pro

2020/03/10 14:30

無事、実装することができました。本当にありがとうございます。 StoreDetailViewControllerをあらかじめ量産するのはやめて、画面遷移時にStoreDetailViewControllerをインスタンス化して尚且つ、その際にgetUserInfoすることにしました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問