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

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

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

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

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

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

Q&A

解決済

1回答

914閲覧

Firebaseから取得した値を代入した変数を、他の画面でも使いまわしたいです。

shippei

総合スコア4

Firebase

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

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

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

0グッド

0クリップ

投稿2020/05/12 10:16

前提・実現したいこと

Swiftで2択のクイズアプリを作成しています。
クイズを提示する「問題画面」と、クイズに答えた後、結果と正答を表示する「正解画面」と「不正解画面」があります。
(画面は分けたいです。)
クイズ番号ごとに、問題文・選択肢1・選択肢2・答えをFirebaseに登録しています。

現在は、下記の通り、QuestionControllerでインスタンス変数をつくり、
Firebaseから取得した値をインスタンスのプロパティの値として代入し、
他の画面で呼び出そうと考えていました。

QuestionController

1import UIKit 2import Firebase 3import FirebaseFirestore 4 5class Question: NSObject { 6 7 var db: Firestore! 8 9 static var question = Question() 10 11 var no: Int = 1 12 13 var answer: String = "answer" 14 15 func getCollection(){ 16 17 let settings = FirestoreSettings() 18 19 Firestore.firestore().settings = settings 20 db = Firestore.firestore() 21 22 db.collection("reading").whereField("no", isEqualTo: no).getDocuments() { 23 (querySnapshot, err) in 24 if let err = err { 25 print("Error getting documents: (err)") 26 } else { 27 for document in querySnapshot!.documents{ 28 if let answer = document.data()["answer"] as? String{ 29 Question.question.answer = answer 30 } 31 } 32 } 33 } 34 } 35}

しかし、

QuestionController

1Question.question.answer = answer

は、getCollection()メソッド内のみ有効であるため、
他の画面で以下のようにアクセスしても初期値が表示されます。

QuestionViewController

1import UIKit 2 3class QuestionViewController: UIViewController{ 4 5 let question = Question.question 6 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 question.getCollection() 10 print(question.answer) //answer(初期値)が表示される 11 }

Firebaseから取得した値を他の画面でも使えるようにするには、どのような処理をすれば良いでしょうか?
根本的な修正が必要なのかもしれませんが、ご教示いただければ大変有難いです。

なお、私はSwift初学者で、Udemyの講座(https://www.udemy.com/course/ios13_swift5_iphone_ios_boot_camp/)を半分受講した程度で、プログラミング歴も1年ほどです。

試したこと

getCollection()メソッドで、return answerとしてanswerを返り値として得た後、
変数に代入しようと考えましたが、
「firebaseからデータを呼び出す際のvoid関数の予期しないnon-void戻り値」というエラーが出ました。

以下の記事を読みましたが、他の画面でも値を使い回すという目的には合っていないと思いました。
リンク内容

以下の記事も読みましたが、取得したい値が複数種類あるので、より簡潔な処理があれば知りたいと思いました。
リンク内容

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

Swift
Xcode version11.4

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

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

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

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

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

guest

回答1

0

ベストアンサー

swift

1 var no: Int = 1

この値を更新しない限り、同じ設問が繰り返されるように思えますがいかがでしょうか。


前回のご質問に対するコメントで「シンクルトン」というデザインですね、という指摘をしたかと思います。
シングルトンとして設計すれば、クラスが変わってもstaticで宣言された値は保持されたままになります。

あまり褒められた例ではありませんが、例えば下記のようなコードを Playground で実行したとします。

swift

1import UIKit 2 3final class Question { 4 private var questions = ["あいうえお", "かきくけこ", "さしすせそ"] 5 static var shardInstance = Question() 6 static var questionNo = 0 7 8 private init () { 9 } 10 11 func nextQuestion() -> String? { 12 if Question.questionNo == questions.count { 13 return nil 14 } 15 16 let question = questions[Question.questionNo] 17 Question.questionNo += 1 18 19 return question 20 } 21} 22 23class A { 24 let q = Question.shardInstance 25 26 init () { 27 } 28 29 func printQuestion() { 30 print(q.nextQuestion() ?? "これ以上設問はありません") 31 } 32 33 deinit { 34 print("シングルトン終了") 35 } 36} 37 38do { 39 // Playground 実行中にインスタンスを終了させるため、意図的に do {} を使う 40 let q1 = A() 41 let q2 = A() 42 43 q1.printQuestion() 44 q2.printQuestion() 45} 46 47do { 48 // スコープが終了しても、シングルトンの中身は引き継がれる 49 let q1 = A() 50 let q2 = A() 51 52 q1.printQuestion() 53 q2.printQuestion() 54}

すると、実行結果は以下の通りとなります。

あいうえお かきくけこ シングルトン終了 シングルトン終了 さしすせそ これ以上設問はありません シングルトン終了 シングルトン終了

この結果からわかることは、シングルトンに関しては実行するクラスが異なっていても、スコープが変わっても同じインスタンスにアクセスするということです。


Firebaseを扱うことと、シングルトンとして扱うことを混同されているように感じます。
まずは、シングルトンがどのような振る舞いをするのか見直してみるのも一つの手かと思いますが、いかがでしょうか。

投稿2020/05/12 10:59

TsukubaDepot

総合スコア5086

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

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

shippei

2020/05/13 01:06

ご丁寧に回答いただき、ありがとうございました。 ご指摘の通り、Firebaseを扱うことと、シングルトンとして扱うことを混同していたようです。 故に、以下の「初期値が表示される」こととその理由は誤っていることがわかりました。 > ```QuestionController Question.question.answer = answer ``` は、getCollection()メソッド内のみ有効であるため、 他の画面で以下のようにアクセスしても初期値が表示されます。 では、いったいどのような構造にすれば、「Firebaseから取得した値を代入した変数を、 他の画面でも使いまわす」という本来の目的が達成できるのでしょうか。 この点についてもアドバイスいただけたら大変有難いです。
TsukubaDepot

2020/05/13 01:11

たとえば、私が回答中に挙げたシングルトンの例は「実際に実行した上で、値などを修正して実行することでその動きを確認する」ということは試されたでしょうか。 その上で、今回追記された質問と上記の例での動きの違いについて、疑問と思われている点はどこになりますでしょうか。 どこでつまづいているか次第でアドバイスも変わりますので、実行の上あらためてご連絡いただければと思います。
shippei

2020/05/14 03:07

コメントありがとうございます! TsukubaDepot様のコメントを最初に読んだとき、 Firebaseとシングルトンの扱いを混同していることに対するご指摘かと考えていました。 そして、例示してくださったコードの冒頭に > private var questions = ["あいうえお", "かきくけこ", "さしすせそ"] とあったので、 そもそも、Firebaseでとってきた値をプロパティの値として代入できない状態だったので、 再度質問をしてしまいました。 これについては、Firebaseは非同期処理のため、  > class QuestionViewController: UIViewController{ let question = Question.question override func viewDidLoad() { super.viewDidLoad() question.getCollection() print(question.answer) } のように、直前でメソッドを呼び出しても、question.answerの値として入らないためだとわかりました。 わたしの今のコードでは、Firebaseからデータを取得するメソッドで条件を指定しており、 noが変わる度にFirebaseにアクセスすることになります。 しかし、TsukubaDepot様が示してくださったように、1回の処理で まずすべてのデータを取得して配列につめ、 その後その配列から条件に合うものをとってくる構造にするのが一般的なのでしょうか? なお、このコメントは質問タイトルと主旨がずれたものでしたら、 今回の質問はTsukubaDepot様の回答をベストアンサーとして閉じ、 また別の質問として投稿します。
TsukubaDepot

2020/05/14 04:47

> わたしの今のコードでは、Firebaseからデータを取得するメソッドで条件を指定しており、 > noが変わる度にFirebaseにアクセスすることになります。 > しかし、TsukubaDepot様が示してくださったように、1回の処理で > まずすべてのデータを取得して配列につめ、 > その後その配列から条件に合うものをとってくる構造にするのが一般的なのでしょうか? まさに、ここが質問者さんが考える(仕様を固める)場所かと思います。 私の例だと、 func nextQuestion() -> String? を呼ぶたびに内部で出題番号をインクリメント(加算)し、出題数が終了すると nil を返すようにしていました。 こちらの方法であれば、たとえば初期段階で一度Firebaseにアクセスし、全ての質問をダウンロードし内部で保持する方法があるかもしれません(イニシャライザは簡潔に記述すべきなので、別途クエリを行うメソッドは準備する必要があるかと思います)。 その一方、たとえば func nextQuestion(questionNo: Int) -> String? のように、出題番号を指定して呼び出す方法も考えられます。 後者の方法であれば、毎回Firebaaseにアクセスすれば良いので、そもそもシングルトンとする必要はなく、Firebaseへのアクセスを隠すだけのクラスメソッドにすればいいようにも思えます(ただし、お気づきの通り非同期処理が絡むのであればその対策も考える必要があります)。 それぞれに長所、短所がありますから、それらを総合的に考えて判断する必要があるかと思います。 これらを考えると、今の課題は 1. Firebaseへのアクセスについての理解 2. シングルトンというデザインパターンの理解 3. 非同期処理への対応に関する理解 の3つを考えなければならず、もしどれかの理解が不足しているのであれば、余計混乱するだけかと思います。 たしかに、全体を俯瞰し見通しの効く設計にすることは大切で、そのために適切なデザインパターンを採用することは大切なのですが、もし、現在の目標が「Firebaseにアクセスし、Q&Aを出すアプリを作ること」が最優先課題であれば、まずはシングルトンは捨て、1と3の解決を優先させてはいかがでしょうか。 出題番号の管理がやや煩雑とはなりますが、出題ごと(つまり、画面が変わるたび)にFirebaseにアクセスするようなコードとしてベタ書きし、全体の道筋が通ってから書き直し(いわゆるリファクタリング)をすればいいかと思いますが、いかがでしょうか。
shippei

2020/05/14 05:07

毎回丁寧なご回答をいただき、本当にありがとうございます。  >出題番号の管理がやや煩雑とはなりますが、出題ごと(つまり、画面が変わるたび)にFirebaseにアクセスするようなコードとしてベタ書きし、全体の道筋が通ってから書き直し(いわゆるリファクタリング)をすればいいかと思いますが、いかがでしょうか。 わたしも、今の自身の理解度であれば、この方法がいいように思います。 おかげで毎回Firebaseにアクセスする方法であればデータを使い回すことはできるようになったので、 当初の課題も解決でき、その後の道筋も示していただけ、本当に感謝しております。
TsukubaDepot

2020/05/14 05:10

私自身がFirebaseをきちんと理解していれば、もっと適切なご回答を差し上げることができたのかと思います。 見通しの良いコードがかければそれに越したことはありませんが、まずは何が目的だったのかはっきりさせられただけでも良かったのかと思います(もちろん、コードやデザインとしては悪手になるかもしれませんが、それは追って修正すればいいかと私は思います)。
shippei

2020/05/14 05:23

とんでもないです!TsukubaDepot様には心から感謝しております。 初めてのSwiftでのオリジナルプロジェクトなので、まずは一通り処理ができることを目指します。 今回は本当にありがとうございました。
TsukubaDepot

2020/05/14 05:31

私もいろいろ勉強できたので良かったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問