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

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

ただいまの
回答率

90.33%

  • Swift

    7712questions

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

  • Firebase

    679questions

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

  • SNS

    105questions

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

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

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 618

hiropome

score 8

どうも、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上の構造は以下画像のようになっています

イメージ説明

//ContactsVCクラス

  override func viewDidLoad() {
        super.viewDidLoad()
            DBProvider.Instance.getContacts();
       }


  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    print("step3")
    return contacts.count;
    }
//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の省略版
    }

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

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

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


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

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{

                let group = DispatchGroup() //追加

                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")

                           group.enter() //追加

                           //以下、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")

                                        }
                                    }


                                }

                                group.leave() //追加
                            })
                        }
                    }
                }

                //追加
                group.notify(queue: .main, execute:  { [weak self] in 
                    self?.delegate?.dataReceived(contacts: contacts)
                })
            }

        }

    } //getContact関数終わり

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/08 17: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は更新されずです、、、

    理解不足で恐れ入りますが、
    アドバイス頂けますと大変幸いです。

    キャンセル

  • 2018/07/08 17:11 編集

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

    キャンセル

  • 2018/07/12 00:04

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

    キャンセル

  • 2018/07/12 00:47 編集

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

    キャンセル

  • 2018/07/12 02: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

    キャンセル

  • 2018/07/12 14:47

    回答に追記しました。

    キャンセル

  • 2018/07/14 21:09

    f-miyuさん

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

    キャンセル

同じタグがついた質問を見る

  • Swift

    7712questions

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

  • Firebase

    679questions

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

  • SNS

    105questions

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