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

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

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

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

Swift

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

SNS

SNS(ソーシャル・ネットワーキング・サービス)は、 人と人とのつながりを促進したり、サポートしたりすることが可能なコミュニティ型のWebサービスです。

Q&A

解決済

1回答

1512閲覧

Swift コードが実行される順番について

ishiishiyay

総合スコア33

Firebase

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

Swift

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

SNS

SNS(ソーシャル・ネットワーキング・サービス)は、 人と人とのつながりを促進したり、サポートしたりすることが可能なコミュニティ型のWebサービスです。

0グッド

0クリップ

投稿2018/07/08 06:57

どうも、Tiktok太郎です!

今夏リリース予定の次世代チャットアプリを作っています。
中高生の間で一世風靡させルために、
助けてください。。。

コードが汚くて、かつ拙い説明で伝わるかどうかとても不安ですが、
何卒よろしくお願いいたします。

TableViewに、友達登録している人だけを表示させるところで詰まっています。

【大まかな流れ】
ContactsVCクラスでgetContacts関数を呼び出す。
getContacts関数内で、Firebaseから、全登録ユーザーの名前やID情報を取得する。
そのうち、現在ログインしているユーザーの友達として登録されてる人の分の情報だけを、Contactクラス配列に入れる。
それをContactsVCクラスで、tableViewに表示させる。

【問題】
コードが思った通りの順番で実行してくれない。
以下記載のコードで、
Step1 → Step2(必要な情報をContact配列に代入) → Step3(Table View表示)
の順で動いて欲しいが、Step1 → Step3 → Step2の順番で実行される。
最後のStep2まで実行されると、Contact配列の中にちゃんと必要な情報が入っているが、
Contact配列が空のままで、必要な情報が代入される(Step2)前にtableView表示(Step3)に進んでしまうので、何も表示されない。。。

Firebase上の構造は以下画像のようになっています

イメージ説明

Swift

1//ContactsVCクラス 2 3 override func viewDidLoad() { 4 super.viewDidLoad() 5 DBProvider.Instance.getContacts(); 6 } 7 8 9 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 10 print("step3") 11 return contacts.count; 12 }
//DBProviderクラス func getContacts() { //以下、Firebaseから全ユーザーの情報を取りに行く Database.database().reference().child("Contacts").observeSingleEvent(of: DataEventType.value) { (snapshot: DataSnapshot) in var contacts = [Contact](); if let myContacts = snapshot.value as? NSDictionary{ for (key, value) in myContacts { if let contactData = value as? NSDictionary { if let name = contactData[Constants.NAME] as? String { let profileImageUrl = contactData[Constants.PROFILE_IMAGE] as! String let id = key as! String; let newContact = Contact(id: id, name: name, profileImageUrl: profileImageUrl);     let friendusername3 = contactData["username"] as! String print("step1") //以下、Firebaseから現在ログインしてるユーザーの友達登録されているユーザーの情報を取りに行く var uid = Auth.auth().currentUser?.uid Database.database().reference().child("Contacts").child(uid!).child("friendusername").observeSingleEvent(of: .value, with: { (snapshot) in if let c = snapshot.value as? [String:AnyObject]{ let myFriendUserName = c[id] as? String if myFriendUserName != nil { //現在ログインしてるユーザーの友達として登録されているユーザー名(myFriendUserName)と、他の全登録ユーザーのユーザー名が一致したとき、 if myFriendUserName! == friendusername3 { contacts.append(newContact) print("step2") } } } }) } } } } //contactsVCのdatareceived func を呼ぶ self.delegate?.dataReceived(contacts: contacts); } } //getContact関数終わり
class Contact { private var _name = ""; private var _id = ""; private var _profileImageUrl = ""; init(id: String, name: String, profileImageUrl: String) { _id = id; _name = name; _profileImageUrl = profileImageUrl; } var name: String? { get { return _name; } } var id: String? { return _id; //上のgetterの省略版 } var profileImageUrl: String? { return _profileImageUrl; //上のgetterの省略版 } }

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

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

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

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

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

guest

回答1

0

ベストアンサー

Step2は非同期で通信した後で動く処理なので、Step3が先に動いちゃうのは当然の結果です。

self.delegate?.dataReceivedで取得したデータをContactsVCに渡しているようですが、渡した後に、tableView.reloadData()を呼び出せば、再度テーブルの更新がされるはずです。


今のgetContactsでは、ユーザーの全情報を取得したあと、dataReceivedが、すぐに呼び出されてしまうので、空のままContactsVCに渡されてしまいます。
DispatchGroupを使えば、すべてのデータをcontactsに追加した後に、dataReceivedを呼び出すようにできます。

swift

1func getContacts() { 2 3//以下、Firebaseから全ユーザーの情報を取りに行く 4 Database.database().reference().child("Contacts").observeSingleEvent(of: DataEventType.value) { 5 (snapshot: DataSnapshot) in 6 7 var contacts = [Contact](); 8 9 if let myContacts = snapshot.value as? NSDictionary{ 10 11 let group = DispatchGroup() //追加 12 13 for (key, value) in myContacts { 14 if let contactData = value as? NSDictionary { 15 if let name = contactData[Constants.NAME] as? String { 16 let profileImageUrl = contactData[Constants.PROFILE_IMAGE] as! String 17 let id = key as! String; 18 let newContact = Contact(id: id, name: name, profileImageUrl: profileImageUrl); 19 20    let friendusername3 = contactData["username"] as! String 21 22print("step1") 23 24 group.enter() //追加 25 26 //以下、Firebaseから現在ログインしてるユーザーの友達登録されているユーザーの情報を取りに行く 27 var uid = Auth.auth().currentUser?.uid 28 Database.database().reference().child("Contacts").child(uid!).child("friendusername").observeSingleEvent(of: .value, with: { (snapshot) in 29 if let c = snapshot.value as? [String:AnyObject]{ 30 let myFriendUserName = c[id] as? String 31 if myFriendUserName != nil { 32 33 34 //現在ログインしてるユーザーの友達として登録されているユーザー名(myFriendUserName)と、他の全登録ユーザーのユーザー名が一致したとき、 35 if myFriendUserName! == friendusername3 { 36 contacts.append(newContact) 37 print("step2") 38 39 } 40 } 41 42 43 } 44 45 group.leave() //追加 46 }) 47 } 48 } 49 } 50 51 //追加 52 group.notify(queue: .main, execute: { [weak self] in 53 self?.delegate?.dataReceived(contacts: contacts) 54 }) 55 } 56 57 } 58 59 } //getContact関数終わり

ついでにアドバイスなのですが、全ユーザーのforループ内で、毎回、同じログインユーザーの登録友達名の情報を取得しているので、かなり無駄です。また、全ユーザー情報を一気に取得するのは、ユーザーが少なければ問題はないかもですが、多くなってくると処理時間的にも問題になってくるかもしれません。
今回の場合、ログインユーザーの友達の情報を取得するのが目的なので、最初にログインユーザーの登録友達名の情報を取得した後に、その取得した友達のIDをから、その友達のみの情報を取得するようにすれば、無駄な処理を省けるかと思います。

投稿2018/07/08 07:23

編集2018/07/12 06:18
f-miyu

総合スコア1625

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

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

ishiishiyay

2018/07/08 08:00

f-miyuさん、 ご回答ありがとうございます!!! DBProviderクラスのgetContact関数内で、 ContactsVCのtableViewを再更新するイメージですか? self.delegate?.dataReceived(contacts: contacts); のすぐ下に、 ContactsVC().myTable.reloadData() を加えましたが、この方法だとContactsVCクラスのmyTableがDBProviderクラスにうまく接続されず、 Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value でクラッシュしてしまいました。 かといって、ContactsVC内でいろんなところに myTable.reloadData(); を挿入してみても、step3→step2のままでtableviewは更新されずです、、、 理解不足で恐れ入りますが、 アドバイス頂けますと大変幸いです。
f-miyu

2018/07/08 08:12 編集

dataReceivedの動いている処理は、ContactsVCに書かれてありませんか。そこで、contactsをセットしていると思うので、その中でreloadData()を呼び出してみてください。
ishiishiyay

2018/07/11 15:04

遅れました! そこのパートには最初からreloadData()を入れていますが、 順番はStep3 → Step2になってしまいます。。。。
f-miyu

2018/07/11 15:52 編集

順番が、Step3 → Step2になるのは通信している以上、仕方がないことです。 それよりも、reloadData()を呼び出しているのに、更新されないというのはおかしいです。 ContactsVCに何か問題があるのかもしれないので、dataReceivedも含めて、もう少しソースを書いていただけないでしょうか?
ishiishiyay

2018/07/11 17:09 編集

f-miyuさん ご返信ありがとうございます!! ちなみに、 DBProviderクラスの、 //以下、Firebaseから現在ログインしてるユーザーの友達登録されているユーザーの情報を取りに行く の下の、 Database.database().reference().child("Contacts").child(uid!).child("friendusername")から始まるブロックを消して、print("step1") のあとに、if条件文なしで、 contacts.append(newContact)を入れると、tableviewにはfirebase上のDBに登録されている全名前がちゃんと表示されます。(友達のみ表示したいので意味ないですが) contactsVCクラスを丸ごと以下に記載します! 大変お手数おかけし申し訳ございませんが、引き続きアドバイス頂けますと幸いです。 class ContactsVC: UIViewController, UITableViewDelegate, UITableViewDataSource, FetchData { @IBAction func goToAddFriend(_ sender: Any) { performSegue(withIdentifier: "AddFriendSegue", sender: nil) } @IBOutlet weak var myTable: UITableView! private let CELL_ID = "Cell"; private let CHAT_SEGUE = "ChatSegue"; private var contacts = [Contact](); var friendName: String = "" var toID: String = "" override func viewDidLoad() { super.viewDidLoad() DBProvider.Instance.delegate = self; //DB Providerのweak var delegateはconfirm fetchdataしたとわからない。教えてあげる DBProvider.Instance.getContacts(); } @IBAction func goToSettings(_ sender: Any) { performSegue(withIdentifier: "SettingsSegue", sender: nil) } @IBAction func goToChatList(_ sender: Any) { performSegue(withIdentifier: "ChatListSegue", sender: nil) } func dataReceived(contacts: [Contact]) { self.contacts = contacts; for contact in contacts { if contact.id == ChatVC().senderID{ AuthProvider.Instance.userName = contact.name!; } } myTable.reloadData(); } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { print("step3") print(contacts.count) return contacts.count; } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath); let user = contacts[indexPath.row] cell.textLabel?.text = user.name cell.imageView?.layer.cornerRadius = 22 //layerが丸くなってもimage viewは四角いまま。imageviewのclipsToBoundsプロパティを有効にする。→下のレイヤーに合わさる。CA Layerの上に薄いUI Viewがある。 cell.imageView?.clipsToBounds = true if let profileImageUrl = user.profileImageUrl { print(profileImageUrl) let url = URL(string: profileImageUrl) //URL(string: String)はStringからURLへの変換 URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in if error != nil { return } DispatchQueue.global().async{ DispatchQueue.main.async{ cell.imageView?.image = UIImage(data:data!) } } }).resume() } return cell; } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){ let user = self.contacts[indexPath.row] friendName = user.name! toID = user.id! performSegue(withIdentifier: CHAT_SEGUE, sender: nil) } //prepare()はsegueされる際に呼び出される関数 override func prepare (for segue: UIStoryboardSegue,sender: Any?){ if segue.identifier == CHAT_SEGUE { let nav = segue.destination as! UINavigationController let chatVC = nav.topViewController as! ChatVC chatVC.friendName = friendName chatVC.toID = toID } } @IBAction func logout(_ sender: Any) { if AuthProvider.Instance.logOut(){ dismiss(animated: true, completion: nil) } } }//class
f-miyu

2018/07/12 05:47

回答に追記しました。
ishiishiyay

2018/07/14 12:09

f-miyuさん 遅れました。 教えていただいたコードで、無事に友達だけを表示させることができました!! 自分一人では到底解決できませんでした。 アドバイスまでいただき本当にありがとうございます。 教えていただいた代替の方法でもコードを書いてみようと思います。 感謝申し上げます!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問