🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Firebase

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

Swift

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

Q&A

解決済

1回答

1440閲覧

Firebaseのリスナーのデタッチ(オブザーバーの適切な削除)

HayashiMasahiro

総合スコア3

Firebase

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

Swift

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

0グッド

0クリップ

投稿2021/03/06 02:33

編集2021/03/06 02:49

発生している問題、知りたいこと

Firebaseのドキュメント
https://firebase.google.com/docs/database/ios/read-and-write?hl=ja#detach_listeners
において、

引用テキストオブザーバーが適切に削除されないと、オブザーバーによってデータがローカルメモリに同期され続けます。

加えて、

さらに、データ(子も含む)が変更されると、そのたびに再トリガーされます

とありますが、その、オブザーバーを適切に削除するとはどういうことかが知りたいです!!

自分の状況

画面①(CategoryShow画面)CategoryIDを元に紐づいたSubliminal群を表示
画面②(SubliminalShow画面)SubliminalIDを元に、Subliminalの情報を表示(この画面で、Subliminalの削除を行う)
②の画面で削除処理を実行(値の変化が発生)すると、別画面①のイベントが発生してしまう。

###前提・実現したいこと
ということは、②の画面で、「Firebaseの指定したデータベース参照でデータの変更(削除)」されても、
別の画面のイベント(取得)のトリガーを発生させなくすればいいのでは、と考えました。

その方法が知りたいです!!

また、データ削除処理を行った時に、データ取得のイベントがトリガーされないようにしたいです!!

問題のコード

画面①における、データ取得のコード

swift

1 //MARK:catetorys/subliminlas/autoIDから、subliminals/autoID/subliminalTitleImageを取得する 2 func getSubliminalTitleImage(){ 3 //MARK:DB迄のreference 4 let ref = Database.database().reference() 5 //MARK:categorys/autoID/subliminals迄のreference 6 let categorySubliminalsRef = ref.child("categorys").child(categoryArray[0].categoryID).child("subliminals") 7 //MARK:subliminals迄のreference 8 let subliminalsRef = ref.child("subliminals") 9 10 print("(A)(func getSubliminalTitleImage() debag2=========================") 11 //MARK:1度目は、snapShotが取得されるまでは、素通りされる 12 //MARK:categorys/autoID/に紐付くsubliminalsの、autoID:true群を取得 13 categorySubliminalsRef.observe(.value) { [self] (snapShot) in 14 print("1.snapShot=========================") 15 print(snapShot) 16 if let snapShot = snapShot.children.allObjects as? [DataSnapshot]{ 17 print("2.空判定後のsnapShot=========================") 18 print(snapShot) 19 for snap in snapShot{ 20 print("(for)3.snap=========================") 21 print(snap) 22 let subliminalAutoID = snap.key 23 print("(for)4.subliminalAutoID=========================") 24 print(subliminalAutoID) 25 //MARK:配列に格納 26 self.subliminalAutoIdArray!.append(subliminalAutoID) 27 print("(for)5.subliminalAutoIdArrayの数=========================") 28 print(self.subliminalAutoIdArray?.count as Any) 29 }//for 30 31 32 for autoID in subliminalAutoIdArray!{ 33 print("debag3") 34 print(subliminalAutoIdArray) 35 36 //MARK:subliminals/subliminalTitleImageを、連番:Stringの形で取得(値は1個だけ) 37 let subliminalTitleImageRef = subliminalsRef.child("(autoID)/subliminalTitleImage") 38 subliminalTitleImageRef.observe(.value) { (snapShot) in 39 print("(A)6.snapShot(subliminalTitleImage)(for)=========================") 40 print(snapShot) 41 //MARK:連番:Stringの内、valueだけを取得 42 let snap = snapShot.value as AnyObject 43 subliminalTitleImageArray?.append(snap) 44 print("(A)7.snapShot(subliminalTitleImage)(for)=========================") 45 print(subliminalTitleImageArray?.count) 46 //MARK:値が格納されて、collectionViewをreloadData()を発動させる 47 subliminalsTableViewCell.collectionView.reloadData() 48 }//observe 49 }//for 50 }//if 51 }//observe 52 }//func 53}//class
画面②における、データ削除のコード

swift

1 //MARK:Firebase/subliminalsから、該当するsubliminalを削除する 2 //MARK:それと共にFirebase/Categorys/autoID/subliminals/からリレーションを削除する処理 3 func deleteSubliminal(){ 4 print("(B)5.deleteSubliminal=============================") 5 print("(B)(self.subliminalArray)") 6 7 8 9 //MARK:categorys/autoID/subliminalsのリレーションを削除========= 10 //MARK:リファレンス作成 11 let categorysRef = Database.database().reference().child("categorys") 12 //MARK:categoryIDを一つづつ取り出して(複数のcategoryが存在する前提) 13 for categoryAutoID in subliminaRelationCategorysKeyArray{ 14 //MARK:Categorys/autoID/subliminals配下の情報を削除 15 //MARK:Categoeys/autoID/subliminals/[string:Bool]型だけど、subliminals/stringだけで削除出来る 16 let categorysSubliminalRef = categorysRef.child("(categoryAutoID)").child("subliminals").child(subliminalID) 17 categorysSubliminalRef.removeValue() 18 } 19 20 21 //MARK:subliminalの削除===================================== 22 //MARK:リファレンス作成 23 let subliminalRef = Database.database().reference().child("subliminals").child(subliminalID) 24 //MARK:subliminalを削除 25 subliminalRef.removeValue() 26 27 28 29 30 31 32 33 34 self.subliminalArray.removeAll() 35 print("(B)5.deleteSubliminal(subliminalArray.removeAll()後)=============================") 36 print("(B)(subliminalArray)") 37 //MARK:該当のsubliminalとcategorysのリファレンスを削除した後、CategoryShow画面に戻りたい 38 self.navigationController?.popViewController(animated: true) 39 }

最後に

まだ、Firebaseが使いこなせておらず、分からないことだらけでして、ご教授いただけたらと思います!!
よろしくお願いします!!

なお、この質問は、前回の質問「Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」のエラーの原因」(https://teratail.com/questions/326068)の続きにもなっていますので、参考にしていただけたらありがたいです!!

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

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

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

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

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

TsukubaDepot

2021/03/06 04:11

オブザーバを削除するだけであれば、以前の質問の過去の回答にあったように DBRef.child("table/orderorder").removeObserver(withHandle: self.observer) みたいな感じでできるかと思います。 ただ、どのタイミングでこれを実行するのがよいのか、という問題だと思います。 たとえば、上記の処理を viewWillDisappear あたりのメソッド内にいれてみたらどうでしょうか。 あと、もしこちらの質問でつづけられるのであれば、前回の質問は閉じていただけますでしょうか。
HayashiMasahiro

2021/03/06 10:37

ご返信ありがとうございま!!!!! 返信遅れて申し訳ありません!! 色々、試行錯誤しております... 「たとえば、上記の処理を viewWillDisappear あたりのメソッド内にいれてみたらどうでしょうか。」 とご指摘頂きましたように、viewWillDisappearでその処理を実行せんべく、 「DBRef.child("自分で設定したchild").removeObserver(withHandle: self.observer)」等の記述を書いておりますが、上記で教えておしえて頂きましたコードの内「(withHandle: self.observer)」の意味がなかなか掴めず苦戦しております。 自分のリサーチ力の無さであり、基本的なことかもしれませんが、何かヒント頂けたらありがたいです....
TsukubaDepot

2021/03/06 10:49

前回のご質問へのコメントで、下記リンクに示した下記の回答がありますが、こちらはご覧になっていますでしょうか。 ここに回答になりそうな内容が記述してあります。 古い記事なので記述が異なる部分もあるかとおもますが、おそらく流れとしては同じだと思います。 https://teratail.com/questions/146400#reply-220817 ちなみに、Firebase のドキュメントを辿っていくと、observe の使い方を見つけることができます。 https://firebase.google.com/docs/reference/swift/firebasedatabase/api/reference/Classes/DatabaseReference#observe_:with: お使いのコードだと、observe が戻す値は使っていませんが、それをクラスのプロパティに持たせておいて、適切なところで oremoveObserver で登録解除するような流れではないのでしょうか。 https://firebase.google.com/docs/reference/swift/firebasedatabase/api/reference/Classes/DatabaseReference#/Detaching-observers
HayashiMasahiro

2021/03/07 04:34

ご返信ありがとうございます!! 現在の状況は、 「https://teratail.com/questions/146400#reply-220817」 で教えて頂いた内容を参考にしまして、 削除処理後、画面遷移の直前(viewWillDisappear)内にて、 「Database.database().reference().child("subliminals").child("\(subliminalID)").removeObserver(withHandle: self.observer)」のコードを記述し、observerを削除する処理を確かめました。 しかし、上手く行きませんでした。おそらく、viewWillDisappearの際には、「Database.database().reference().child("subliminals").child("\(subliminalID)")」の参照自体が削除されている為と思われます(確信はありませんが..)。 そこで、まだ参照が残っている段階で、このコード自体に問題がないか確かめるべく、 「Database.database().reference().child("subliminals").child("\(subliminalID)").removeObserver(withHandle: self.observer)」のコードを、(まさに画面遷移の際、処理を止めたいと考えている、初期にFirebaseから情報を取得するメソッドの中)で使用すると、どうも上手く作動しているようです。 「どうも上手く作動しているよう」と判断したのは、 同じファイルの中の他のメソッドですが、同じ参照に情報を追加した時に、obseverを削除している為か、追加した情報が表示されなくなった為です!! やはり、以前、ご指摘頂いたように、どのタイミングでそれを記述していくのか、切り分けながら、考えているところです。 もう少し、粘ってみます!!!
HayashiMasahiro

2021/03/07 07:16

「observerのデタッチをいつ行うのか」の条件ごとの現在の状況です!! 条件①「viewWillDisappear内」================================ (viewWillApper時)fetchSubliminalData()→情報取得のメソッド updataSubliminalPhotos()→情報の追加のメソッド deleteSubliminal()→削除処理の後、画面遷移 (viewWillDisappear時)detachEventListener()→observerのデタッチ 画面遷移後も、fetchSubliminalData()が反応しているようで...上手く行かず!! 条件②「fetchSubliminalData内で実装」============================ (viewWillApper時)fetchSubliminalData()→情報取得とobserverのデタッチ updataSubliminalPhotos()→情報の追加 deleteSubliminal()→削除処理の後、画面遷移 画面遷移後、observerのデタッチされている為、fetchSubliminalData()が反応せず成功!! しかし、observerのデタッチされている為、updataSubliminalPhotos()による情報の追加は画面に反映されす!! 条件③「fetchSubliminalData()内で実装し、updataSubliminalPhotos()内でもう一度(情報が追加される度)fetchSubliminalData()を呼ぶ」================================= (viewWillApper時)fetchSubliminalData()→情報取得とobserverのデタッチ updataSubliminalPhotos()→情報の追加と、再度、fetchSubliminalData()を呼び、情報取得とobserverのデタッチ deleteSubliminal()→削除処理の後、画面遷移 viewWillApper時、observerのデタッチは行われても、 updataSubliminalPhotos()時、再度、fetchSubliminalData()を読み込むことで追加の情報も反映(その際にobserverもデタッチ), 削除して、画面遷移時も、observerのデタッチは行われている為、今のところ、不具合は見つからず!!!!!!
HayashiMasahiro

2021/03/07 07:22

上の解答の条件③で、今のところはw不具合が出ていない感じですので、一旦、次の実装へと進みたいと思います!! TsukubaDepotさんに、お礼を言います!! 何度も、考えるとっかりとなるアドバイスをしてくださり、本当にありがとうございました!! 本当に助かりました!!! この強引な実装がベストプラクティスがどうかは分かりませんが(おそらく違うでしょうw)、 ここまでアドバイスなしに行きませんでした!! 本当にありがとうございました!!!!!!!!!!!!!!!!!!!! 一旦、この質問は閉じさせて頂きます!!!!!!!!!!
TsukubaDepot

2021/03/07 07:39

多分存在すると思うのですが、オブザーバを設定せず、ワンショット(一回だけ)データを取ってくる方法はあると思いますので、データの更新に連動する必要がなければ、それをつかうのが良いのかもしれません。
HayashiMasahiro

2021/03/08 03:55

ご返信ありがとうございます!! 心中では、全然しっくりきてないのが、実際のところでして... 「オブザーバを設定せず、ワンショット(一回だけ)データを取ってくる方法はあると思いますので」 に関しまして、これをヒントにして色々検索してみます!! そもそも、自分がobserverを削除したいと考えている画面(B)に関して言いますと、 「Firebaseに保存している数あるsubliminalの中からautoIDを元にして詳細情報を表示」 そして、この画面(B)上で 「このsubliminalに写真を登録(追加)出来、Firebaseに追加されるや、それが画面に表示される」 という実装でありました。 「Firebaseに追加されるや、それが画面に表示されるという処理」に関して言いますと、自分は cameraPickerが閉じると再びviewwillApperが起動するから、そこに記述したfetchSubliminalData()が発動しtableView.ReloadData()されると勘違いしていました。 値の変更があったから、observerが発動していたとは.....viewwillApperも起動していなかったですし... 自分の実装照らし合わせて、助言について考えまするに、「データの更新に連動しなくてはならない画面」であります。その場合のobserverの切りどころは、まだしっくりきていないのが実情です。
TsukubaDepot

2021/03/08 04:07

「cameraPickerが閉じると再びviewwillApperが起動するから」 「.viewwillApperも起動していなかったですし...」 これですが、元の画面に戻っても、元の画面の View Controller の viewWillAppear が呼ばれない、ということでしょうか。 それであれば、この辺りの問題も関係してくるので、合わせて整理されるといいかもしれません。 https://qiita.com/fummicc1_dev/items/a30e3cbfbf1148b0ec84
guest

回答1

0

自己解決

ベストな解決方法ではなく、一旦、我流の解決で次へ進みます!!!!!

イメージ説明

投稿2021/03/07 07:25

HayashiMasahiro

総合スコア3

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問