LoginSignup
2
4

More than 5 years have passed since last update.

データのfetchingにおけるRealtime DatabaseとCloud Firestoreの比較

Last updated at Posted at 2019-01-15

自分はSmartNewsやGunosyのようなメディアアプリを作成する際に、FirebaseのRealtime Databaseを最初使っていましたが、途中からCloud Firestoreへ移行しました。
移行前のコードと移行後のコードを書いておきます。

Realtime Databaseでの記事情報の取得

まずは記事情報を取得するためのモデルを書きます。
DataSnapshot型はFirebaseからデータを取得する際の、Realtime Database特有のデータ型です。
Realtime Databaseから、DataSnapshot型のデータを取得させたら、それをこのモデル(ArticleData.swift)を経由させます。
ArticleData.swiftの中で、DataSnapshot型を別途UITableViewCellなどで再利用可能な汎用的な型に変えていくという流れです。

ArticleData.swift
import UIKit
import Firebase
import FirebaseDatabase


class ArticleData: NSObject {

    var id: String?
    var articleUrl:String?
    var genreName:String?
    var sourceName:String?
    var summary:String?
    var imageUrl:String?
    var imageString: String?
    var titleStr: String?
    var likes: [String] = []
    var isLiked: Bool = false
    var clipSumNumber:Int?
    var relatedArticleIDs:[String] = []

    //snapshotにはRealtime Databaseから取ってきたDataSnapshot型の記事情報が入る。
    //myIdには同様にUseのIDが入る。このIDは"いいね"数を計算する際に使用する。
    init(snapshot: DataSnapshot, myId: String) {
        self.id = snapshot.key

        let valueDictionary = snapshot.value as! [String: AnyObject]

        //以下keyに対応させて引っ張ってきている
        self.articleUrl = valueDictionary["articleUrl"] as? String
        self.genreName = valueDictionary["genreName"] as? String
        self.imageUrl = valueDictionary["imageUrl"] as? String
        self.sourceName = valueDictionary["sourceName"] as? String
        self.titleStr = valueDictionary["titleStr"] as? String
        self.summary = valueDictionary["summary"] as? String

        if let relatedArticleIDs = valueDictionary["relatedArticleIDs"] as? [String]{
            self.relatedArticleIDs = relatedArticleIDs
        }

        //Realtime Databaseのkey"likes"には、配列形式で記事に"いいね"したユーザーのIDが格納されている。それを[String]型にキャストして取り出している。
        if let likes = valueDictionary["likes"] as? [String] {
            self.likes = likes
        }

        for likeId in self.likes {
            //配列likesのなかに自分のユーザーIDがあれば、"いいね"済み=trueということ。
            if likeId == myId {
                self.isLiked = true
                break
            }
        }
        //"いいね"数の計算
        self.clipSumNumber = likes.count
    }
}

続いて、記事情報をfetchする関数を記述していきます。

ViewController.swift

/*class宣言の中*/

var articleArray:[ArticleData] = []

func fetchArticleData() {
    articleArray = [] //呼ぶ際に空にする。これがないとfetchArticleData()が呼ばれるたびにarticleArrayが倍増していくことになる。
    if Auth.auth().currentUser != nil { //Firebase Authでログインしているかどうかをチェック
        let articlesRef = Database.database().reference().child("ArticleData")//"ArticleDataはFirebaseのパス"
        articlesRef.observe(.childAdded, with: {snapshot in 
            if let uid = Auth.auth().currentUser?.uid {
                let articleData = ArticleData(snapshot: snapshot, myId: uid)
                if self.articleArray.count < 14 { //記事数を14記事までにする
                    self.articleArray.insert(articleData, at: 0)
                }

                self.tableView.reloadData() //これがないと、肝心の記事がload...表示されない。
                SVProgressHUD.dismiss() //SVProgressHUDについての説明は省略
            }
        })

        //.childChandedで、いいねボタンタップ時の挙動をキャッチしている。
        articlesRef.observe(.childChanged, with: { snapshot in
            if let uid = Auth.auth().currentUser?.uid {
                let articleData = ArticleData(snapshot: snapshot, myId: uid)

                var index: Int = 0 //変更があった記事のindexを入れるため
                for article in self.articleArray { //articleArrayには上の.childAddedで引っ張ってきた記事が配列として入っている。
                    if article.id == articleData.id { //変更があった記事のidと、.childAddedで引っ張ってきた記事のidが同じだった場合のindexを取得しようとしている。
                        index = self.articleArray.index(of: article)!
                        break
                    }
                }

                //差し替えるために一度削除
                self.articleArray.remove(at: index)
                //削除したところに更新済みのデータを追加
                self.articleArray.insert(articleData, at:index)

                SVProgressHUD.dismiss()
            }
        })        
    }
}

いいねボタンを押した時の挙動を書くコードにも変更点があるので載せておきます。
まずはRealtime Databaseを利用時

ViewController.swift
//TableViewのデリゲートメソッド:cellForRowAt。TableViewCellの外観を定義します。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: R.nib.listCell, for:indexPath)  else { return UITableViewCell()}
    cell.setCellInfo(articleData: articleDataArray[indexPath.row])

    //このclipButtonは作成したCellの内容を定義したxib/nibファイルで、Outletで宣言したUIButtonです。このclipButtonに.addTargetを用いてhandleButtonメソッドを付加しています。
    cell.clipButton.addTarget(self, action: #selector(handleButton(sender:event:)), for:   UIControl.Event.touchUpInside)
    return cell
}


@objc func handleButton(sender:UIButton, event:UIEvent) {
    // タップされたセルのインデックスを求める
    let touch = event.allTouches?.first
    let point = touch!.location(in: self.tableView)
    let indexPath = tableView.indexPathForRow(at: point)

    // 配列からタップされたインデックスのデータを取り出す
    let articleData = articleArray[indexPath!.row]

    // Firebaseに保存するデータの準備
    if let uid = Auth.auth().currentUser?.uid {
        if articleData.isLiked {
            // すでにいいねをしていた場合はいいねを解除するためIDを取り除く
            var index = -1
            for likeId in articleData.likes {
                if likeId == uid {
                    // 削除するためにインデックスを保持しておく
                    index = articleData.likes.index(of: likeId)!
                    break
                }
            }
            articleData.likes.remove(at: index)

        } else {
            articleData.likes.append(uid)    
        }

        // 増えたlikesをFirebaseに保存する
        let articleRef = Database.database().reference().child("ArticleData").child(articleData.id!)
        let likes = ["likes": articleData.likes]
        articleRef.updateChildValues(likes)
    }    
}

Cloud Firestoreでの記事情報の取得

先と同じく、まずはモデルを書きます。

ArticleQueryData.swift
class ArticleData: NSObject {

    var id: String?
    var articleUrl:String?
    var genreName:String?
    var sourceName:String?
    var summary:String?
    var imageUrl:String?
    var imageString: String?
    var titleStr: String?
    var date: NSDate?
    var likes: [String] = []
    var isLiked: Bool = false
    var clipSumNumber:Int?
    var tags: String?
    var relatedArticleIDs:[String] = []

    var isCommented: Bool = false
    var commenterIDs:[String] = []

    init(snapshot: QueryDocumentSnapshot, myId: String) {
        self.id = snapshot.documentID

        let valueDictionary = snapshot.data() 

        //以下keyに対応させて引っ張ってきている
        self.articleUrl = valueDictionary["articleUrl"] as? String
        self.date = valueDictionary["date"] as? NSDate
        self.genreName = valueDictionary["genreName"] as? String
        self.imageUrl = valueDictionary["imageUrl"] as? String
        self.sourceName = valueDictionary["sourceName"] as? String
        self.titleStr = valueDictionary["titleStr"] as? String
        self.summary = valueDictionary["summary"] as? String
        self.tags = valueDictionary["tags"] as? String         

        if let relatedArticleIDs = valueDictionary["relatedArticleIDs"] as? [String]{
            self.relatedArticleIDs = relatedArticleIDs
        }

        if let likes = valueDictionary["likes"] as? [String] {
            self.likes = likes
        }

        for likeId in self.likes {
            if likeId == myId {
                self.isLiked = true
                break
            }
        }
        self.clipSumNumber = likes.count
    }
}

基本的にモデルに関しては、Realtime Databaseと変更点はあまりありません。

続いてFirestoreが記事情報をfetchしていきます。
一度取得した情報を、その後の変更点もキャッチし続けるためには、
.getDocuments() { querySnapshot }ではなく、.addSnapshotListener { querySnapshot }を使います。
そして、その中で、.documentChanges.forEach{ diff }を使用するのがポイントです。
diffには差分の情報が入ります。

ViewController.swift

/*class宣言の中*/

var articleDataArray:[ArticleQueryData] = []

func fetchArticleData() {
    //ログイン済み(uidを取得済み)であることを確認
    if let user = Auth.auth().currentUser {
        let ref = Firestore.firestore().collection("articleData")
        let uid = user.uid

        ref.addSnapshotListener { querySnapshot, err in
            if let err = err {
                print("Error fetching documents: \(err)")
            } else {
                self.articleDataArray = [] //これがないと、変更のたびに記事量が倍になっていく
                for document in querySnapshot!.documents {
                    let articleData = ArticleQueryData(snapshot: document, myId: uid)

                    if self.articleDataArray.count < 14 { //記事数を14記事までにする
                        self.articleDataArray.insert(articleData, at: 0)
                    }
                    self.tableView.reloadData()
                    SVProgressHUD.dismiss()
                }
                /*
                querySnapshot!.documentChanges.forEach{diff in
                    if diff.type == .added {
                        print("added: \(diff.document.data())")
                    } else if diff.type == .modified {
                        print("modified: \(diff.document.data())")                        
                    } else if diff.type == .removed {
                        print("removed: \(diff.document.data())")
                    }
                }*/
            }
        }
    }
}

diff.type == .addedは、初回ロード時も呼ばれるようです。
diff.type == .modifiedは、"いいね"ボタンを押して実際にFirestoreの情報を編集した時に呼ばれるようです。
diff.type == .removedは、まだ試していないです。documentの内容の削除ではなく、document自体を削除する際に呼ばれるのでは?と推測しています。

.documentChanges.forEachが必要かと思っていたら、なんかこれを使わなくても、更新をキャッチするみたいですね。差分を再利用したい時に用いるのかもしれません><
詳しい方は教えてください。

続いてFirestore利用時のいいねの挙動です。最後の部分以外はRealtime Database利用時と一緒なので、前半は省略します。

ViewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //省略
}


@objc func handleButton(sender:UIButton, event:UIEvent) {
    //省略
    if let uid = Auth.auth().currentUser?.uid {
    //省略

        // 増えたlikesをFirebaseに保存する
        let articleRef = Firestore.firestore().collection("articleData").document(articleData.id!)
        let likes = ["likes": articleData.likes]

        articleRef.updateData(likes){ err in
            if let err = err {
                print("Error adding document: \(err)")
            } else {
                print("Document successfully written!")
            }
        }
    }    
}

firestoreの差分データ取得
【FireBase】 FireStoreで簡単なぼっちSNSアプリを作ってみました
Firestoreを試してみた //こちらのサイトのリスナーを設定するのパートから参考になります。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4