前提・実現したいこと
以下のコードを実装した後、発生するエラーに関して質問があります。
仮に、遷移元の画面をA、このコードが書かれている画面をBとします。
以下のコードは、**「B画面にて、Firebaseから特定の値を削除し、self.navigationController?.popViewController(animated: true)で、遷移元の画面(A)に戻る処理」**を実装しています。
このコードにより、確かにfirebase上の値は削除され、遷移元の画面(A)には戻るのですが、
その後、**何故か、「既に遷移して、シュミレーターの画面も切り替わっているのにB画面のファイルでエラーが発生」**してしまいます!!
swift
1class SubliminalShowViewController: UIViewController { 2 3 4 5 //MARK:parts===================================== 6 @IBOutlet weak var tableView: UITableView! 7 8 9 //MARK:変数======================================== 10 //MARK:categoryIndex → RecategoryShowから引き継いだ一つのcategoryのインスタンス 11 var categoryArray = [Categorys]() 12 //MARK:RecategoryShowに紐付くsubliminalID(前画面からの引継ぎ) 13 var subliminalID = String() 14 //MARK:subliminalIDを元に、Firebaseからの取得したsubliminalの情報(tableViewで利用) 15 var subliminalArray = [Subliminals]() 16 //MARK:subliminals/autoID/categoryID群の配列(subliminalArrayから取得) 17 var subliminaRelationCategorysArray = [String:Bool]() 18 //MARK:subliminaRelationCategorysArrayの連想配列型から、keyだけ取得 19 var subliminaRelationCategorysKeyArray = [String]() 20 //MARK:subliminalに紐付いている写真の枚数(追加したphotoをFirebaseに保存する時に使用) 21 var subliminalPhotosCount = Int() 22 23 24 //MARK:初期設定 25 override func viewDidLoad() { 26 super.viewDidLoad() 27 } 28 29 30 31 //MARK:毎回 32 override func viewWillAppear(_ animated: Bool) { 33 super.viewWillAppear(animated) 34 print("0.categoryArray(このsubliminalが所属しているcategoyのautoID)==================") 35 print(self.categoryArray[0].categoryID) 36 print("1.categoryArray(所属しているカテゴリー名)=====================================") 37 print(self.categoryArray[0].categoryTitle) 38 print("2.subliminalID(表示しているsubliminalのID)====================================") 39 print(self.subliminalID) 40 settingTableView() 41 settingNavigation() 42 settingUINib() 43 fetchSubliminalData() 44 } 45 46 47 48 49 //MARK:setting================================== 50 //MARK:tableView 51 func settingTableView(){ 52 self.tableView.delegate = self 53 self.tableView.dataSource = self 54 //MARK:未使用のCellを消去 55 tableView.tableFooterView = UIView() 56 //MARK:Cell上のButtonの遅延を無くす 57 tableView.delaysContentTouches = false 58 59 } 60 61 62 63 //MARK:action===================================== 64 65 66 //MARK:「削除」Button 67 @objc func showAlertForDelete(){ 68 let alertController = UIAlertController(title: "Subliminalの削除", message: "本当に削除してもよろしいですか?", preferredStyle: .alert) 69 let action1 = UIAlertAction(title: "キャンセル", style: .cancel) { (alert) in 70 } 71 let action2 = UIAlertAction(title: "削除", style: .destructive) { (alert) in 72 self.deleteSubliminal() 73 } 74 alertController.addAction(action1) 75 alertController.addAction(action2) 76 self.present(alertController, animated: true, completion: nil) 77 } 78 79 80 81 82 83 84 85 86 87 88 89 //MARK:method=================================== 90 //MARK:FirebaseからSubliminalの情報を収集 91 //MARK:この場合、Subliminalは一つだけ取得 92 func fetchSubliminalData(){ 93 //MARK:subliminal配下の一意のautoIDまでreferenceを取得 94 let subliminalRef = Database.database().reference().child("subliminals").child("(subliminalID)") 95 //MARK:reference配下の情報を塊にして取得 96 subliminalRef.observe(.value) { [self] (snapShot) in 97 //MARK:通信が終わったら呼ばれる 98 //MARK:snapShotからdataを引き出す 99 let snapShotData = snapShot.value as AnyObject 100 //MARK:取得したデータから、keyでvalueを取得(この段階では何型か判断出来ない) 101 let subliminalId = snapShotData.value(forKey: "subliminalId") 102 let subliminalTitle = snapShotData.value(forKey: "subliminalTitle") 103 let subliminalTitleImage = snapShotData.value(forKey: "subliminalTitleImage") 104 let subliminalDescription = snapShotData.value(forKey: "subliminalDescription") 105 let subliminalPhotos = snapShotData.value(forKey: "subliminalPhotos") 106 let categoryID = snapShotData.value(forKey: "categoryID") 107 //MARK:空判定する方法 108 var postDate:CLong? 109 if let postedDate = snapShotData.value(forKey: "postData") as? CLong{ 110 postDate = postedDate 111 }else{ 112 print("kara") 113 } 114 115 //MARK:postDataを時間に変換 116 let timeString = self.convertTimeStamp(serverTimeStamp: postDate!) 117 //MARK:Subliminalsモデルのインスタンスを生成(ここでダウンキャストしている) 118 let subliminal = Subliminals( 119 subliminalTitle: subliminalTitle as! String, 120 subliminalTitleImage: subliminalTitleImage as! String, 121 subliminalId: subliminalId as! String, 122 subliminalDescription: subliminalDescription as! String, 123 subliminalPhotos: subliminalPhotos as! Array<Any>, 124 postData: timeString, 125 categoryID: categoryID as! [String : Bool] 126 ) 127 //MARK:配列の中を空へ 128 self.subliminalArray.removeAll() 129 //MARK:配列へSubliminalsモデルのinstanceを格納(tableViewで利用) 130 self.subliminalArray.append(subliminal) 131 //MARK:廃列へ格納したインスタンスから、subliminalPhotosの数を取得(新しくsubliminalPhotosをFirebaseに追加する時に利用する(Firebase保存で利用)) 132 self.subliminalPhotosCount = self.subliminalArray[0].subliminalPhotos!.count 133 print("3.subliminalPhotosCount=======================") 134 print(subliminalPhotosCount) 135 //MARK:subliminalが所有しているcategoryIDの連想配列群を取得 136 self.subliminaRelationCategorysArray = self.subliminalArray[0].categoryID 137 //MARK:subliminaRelationCategorysArrayの連想配列群から、keyだけを配列化 138 self.subliminaRelationCategorysKeyArray.append(contentsOf: subliminaRelationCategorysArray.keys) 139 print("4.subliminalが所属しているCategoryのautoID群=======================") 140 print(subliminaRelationCategorysKeyArray) 141 self.tableView.reloadData() 142 } 143 } 144 145 146 147 148 //MARK:時間を人間が読める形に変換する 149 func convertTimeStamp(serverTimeStamp:CLong) -> String{ 150 let x = serverTimeStamp / 1000 151 let date = Date(timeIntervalSince1970: TimeInterval(x)) 152 let formatter = DateFormatter() 153 formatter.dateStyle = .long 154 formatter.timeStyle = .medium 155 return formatter.string(from: date) 156 } 157 158 159 160 161 162 163 //MARK:選択した画像を、Firebase/subliminals/subliminalPhotosに保存 164 func updataSubliminalPhotos(selectedImage:UIImage){ 165 //MARK:リファレンスの形成==================================== 166 //MARK:subliminals/autoID/subliminalPhotos迄のreference取得 167 let subliminalPhotoRef = Database.database().reference().child("subliminals").child("(subliminalID)").child("subliminalPhotos") 168 //MARK:Storageのreference取得 169 let storage = Storage.storage().reference(forURL: "gs://subliminalapp-1936e.appspot.com") 170 //MARK:Storageへ保存の為のkeyを作成(必須ではない) 171 let key = subliminalPhotoRef.child("subliminalPhotos").childByAutoId() 172 //MARK:StorageServerにパスを形成 173 let imageRef = storage.child("SubliminalImageView").child("(String(describing: key)).jpg") 174 175 176 //MARK:画像をデータ型へ変換================================== 177 //MARK:Data()のinstance 178 var subliminalImageViewData:Data = Data() 179 //MARK:圧縮データを作成 180 subliminalImageViewData = (selectedImage.jpegData(compressionQuality: 0.01))! 181 182 183 184 185 //MARK:画像をStorageへ保存================================== 186 let uploadTask = imageRef.putData(subliminalImageViewData, metadata: nil) { (metadata, error) in 187 //MARK:エラー処理 188 if error != nil{ 189 print(error as Any) 190 return 191 } 192 print("Storage保存成功!!") 193 //MARK:成功時(urlを取得) 194 imageRef.downloadURL { (url, error) in 195 //MARK:エラー処理 196 if error != nil{ 197 print(error as Any) 198 return 199 } 200 print("downloadURL成功") 201 //MARK:成功時(databaseへ値を保存) 202 subliminalPhotoRef.updateChildValues([ String(self.subliminalPhotosCount) : url?.absoluteString as Any]) 203 } 204 } 205 }
発生している問題・エラーメッセージ
swift
1 //MARK:postDataを時間に変換 2 let timeString = self.convertTimeStamp(serverTimeStamp: postDate!) 3 4 ここで以下のエラーが発生してしまいます。 5 「Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」 6
全体のロジックが見えないのでなんとも言えませんが、遷移元の画面(画面A)に戻ったからといって、即座に画面Bのインスタンスが削除されるとは限りません。
エラーが出ている postDate 周辺の呼び出しロジック(タイミング)もいただいたコードだけでは判断できないのではないでしょうか。
ViewControllerに限らず、クラスのインスタンスが破棄される直前に処理されるブロックに deinitがありますので、それらも使ってライフサイクルをきちんと確認されてはいかがでしょうか。
たとえば、画面A/画面BのviewDidApperやviewWillDisApper などのメソッドにもデバッグ用のメッセージを入れて、画面遷移時にどのようなタイミングで処理が行われるのか、またいつインスタンスが破棄されるのか確認すると少しは捗るかもしれません。
ご返信ありがとうございます!!
「遷移元の画面(画面A)に戻ったからといって、即座に画面Bのインスタンスが削除されるとは限りません。」に関して。
これは知りませんでした!!結構、これが当たっているのではないかと思いました。
このラインでまずは調べてみます!!
「エラーが出ている postDate 周辺の呼び出しロジック(タイミング)もいただいたコードだけでは判断できないのではないでしょうか。」
これに関しまして、別の質問を作成し、その中でロジック周りのコードを載させて頂きいましたので、
お時間ありましたら、是非、意見を下さい!!
「「Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value」のエラーの原因②」というタイトルです!!
https://teratail.com/questions/326158?modal=q-comp
「ライフサイクルをきちんと確認されてはいかがでしょうか」
これに関しまして、print()を色んなとこに書いており、処理の流れを調べますと、
遷移元に戻り、その途中で遷移前のprint()が呼び出されているようで....
もう少し、原因を調べる必要がありそうです.....
同じ内容で別の質問を立ててしまうと、重複した内容ということでマイナス評価がついてしまうことがあります(知らない人が勢いで付けてしまいます)。
内容的にはこのご質問と同じなので、このご質問を編集し、新たな質問は運営に削除依頼されたほうがいいかと思います。
ありがとうございます!!
早速、削除申請を出しておきました!!
Firebaseやったことないので外している可能性がありますが、
subliminalRef.observe(.value)
でその後に続くブロック(クロージャ)は、値が更新されるたびに呼び出されるということはないでしょうか。
また、この監視については、明示的に削除する必要があるようにドキュメントを見る限り読み取れます。
https://firebase.google.com/docs/database/ios/read-and-write?hl=ja#read_value_events
「さらに、データ(子も含む)が変更されると、そのたびに再トリガーされます。」
とあるので、データが削除された瞬間も呼び出されるのでないでしょうか。
ただし、その際はpostDateは存在しないので nil となり、該当する場所でクラッシュしているような気がします。
直前でnil判定していますが、guard-letで早期リターンしているわけはないので、当然ここには nil のまま到達し、強制アンラップで落ちてしまいます。
なので、該当する ViewController を dismiss する際には明示的にスナップショットリスナー?を削除する必要があるのでないでしょうか
https://firebase.google.com/docs/database/ios/read-and-write?hl=ja#detach_listeners
あるいは、ワンショットでデータを取得する方法もあるかもしれません。
関連質問がここにあるのですが、これは参考になりませんでしょうか。
https://teratail.com/questions/146400
ご返信ありがとうございます!!
あれからご指摘頂いた内容「ライフサイクル」周りについて自分なりに実験しておりました。
遷移元を変更したり、遷移しなかった処理にしてみたり、削除処理に関しても、Firebaseの違う参照から削除したりしました。
それら過程の中で、どうしても(A)画面の処理が実行され、これは一体何なんだ?と昨日考えていましたが、
昨日のアドバイス
「ブロック(クロージャ)は、値が更新されるたびに呼び出されるということはないでしょうか。
また、この監視については、明示的に削除する必要があるようにドキュメントを見る限り読み取れます。」
「さらに、データ(子も含む)が変更されると、そのたびに再トリガーされます。」」
を読ませて頂き、そういうことか!!と発見する思いでした!!
ありがとうございます!!
おそらく、値の変更により、再度、読み込まれているのだと思います。
そう仮定すると、どう処理をしていくべきなのか、再度、検討いたします!!
ありがとうございます!!
また、質問させて下さい!!
もし、解決の見込みがあれば、自己解決というでこのご質問を閉じていただけますでしょうか。わかったことを簡潔に整理していただければ違う方の参考になるかと思います。
すぐには解決できなくとも、「こういう方向で解決できそうだ」という方針を回答に書かれて、一旦閉じられるというてもあるかと思います。
いずれにしても未解決で放置は良くないと思いますので、ご協力いただければと思います。
ご返信遅くなりすいませんでした!!
色んな助言、本当にありがとうございます!!!!
勝手ではありますが、上の意見のスクショを貼らせて頂き、自分の意見を添えて解答といたします!!
回答1件
あなたの回答
tips
プレビュー
