【问题标题】:How can I properly leverage Firestore realtime updates in swift using the onSnapshot() method?如何使用 onSnapshot() 方法在 swift 中正确利用 Firestore 实时更新?
【发布时间】:2019-12-16 15:23:40
【问题描述】:

我是 Google Firestore 的新手,而且一般都很快,我想知道如何正确设置 onSnapshot() 方法,以便我的视图控制器可以自动接收更新并适应所有情况。

我有一个非常简单的结构 (Llama),用于对我的数据进行建模。通过遵循这个优秀的教程,我能够构建一个基本的 Firestore 设置:(https://youtu.be/XwXEsKRYUXU)

但是,我发现在实现我自己的代码版本时,我只知道如何允许在服务器上创建文档。如果它在服务器上发生更改,我希望能够更新我的应用程序中的对象,并且我也希望能够删除一个。我目前不知道如何做到这一点。

此外,在更新我的应用程序中的对象时,有没有办法只更新更改的字段,而不是覆盖整个对象。

我希望能够实现的是一个数据库模型,它可以在后台在托管服务器和用户设备之间无缝同步,如果发生冲突,可以将任何人的更改合并在一起(由最后修改文档的人解决。 )

我已经详细阅读了 Google 关于该主题的文档,尽管我确定答案就在那里,但我个人还没有达到可以完全理解它们的 swift 水平:(https://cloud.google.com/firestore/docs/how-to)

数据模型:

import Foundation
import Firebase

protocol DocumentSerializable {
    init?(dictionary:[String:Any])
}

struct Llama {

    var name: String
    var color: String
    var gender: String

    init(name: String, color: String, gender: String) {
        self.name = name
        self.color = color
        self.gender = gender
    }

    var dictionary:[String:Any] {
        return [
            "name":name,
            "color":color,
            "gender":gender
        ]
    }

}

extension Llama:DocumentSerializable {
    init? (dictionary: [String:Any]) {
        guard
            let name = dictionary["name"] as? String,
            let color = dictionary["color"] as? String,
            let gender = dictionary["gender"] as? String
            else {return nil}
        self.init(name: name, color: color, gender: gender)
    }
}

查看控制器代码:

    var llamas = [Llama]()

    override func viewDidLoad() {
        super.viewDidLoad()
        checkForUpdates()
    }

    func checkForUpdates() {
        let firestore = Firestore.firestore()
        firestore.collection("Llama").addSnapshotListener{
            QuerySnapshot, error in
            guard let snapshot = QuerySnapshot else {return}
            snapshot.documentChanges.forEach {
                update in
                if update.type == .added {
                    self.llamas.append(Llama(dictionary: update.document.data())!) // Works great!
                }
                if update.type == .modified {
                    // How can I update the correct llama object, hopefully just the field(s) that changed?
                }
                if update.type == .removed {
                    // How can I remove the correct llama object?
                }
            }
        }
    }

如视图控制器代码所示,我有一个在视图控制器打开时调用的函数,该函数初始化 addSnapShotListener() 方法。按原样运行的代码不会出现任何错误或警告。我该如何前进?

【问题讨论】:

  • 听起来您想在添加、修改或删除某些内容时监听数据库的更改。该文档涵盖了View changes between snapshots
  • 谢谢!我知道这个页面刚开始,但很高兴知道我正在关注文档的正确部分。

标签: swift firebase google-cloud-firestore


【解决方案1】:

这个问题有几个部分,要解决所有这些问题将是一个很长(呃)的答案。这是一个解决方案,它最初会加载您的 dataSource 数组,然后监视和处理谨慎的“对象”的添加、修改和删除事件,例如文件。

在本例中,我们将使用一个跨设备跟踪喜爱的葡萄酒的应用。我们会记录葡萄酒的名称、产地和评级。

结构是这样的

wines
   doc_0
      name: "Insignia"
      rating: "98"
      state: "CA"
   doc_1
      name: "Quilceda Creek"
      rating: "99"
      state: "WA"

doc_0、doc_1 等是添加文档时自动创建的文档 ID。

那么我们需要一个类来容纳每种酒

class WineClass {
    var wine_id = ""
    var name = ""
    var rating = ""
    var state = ""

    init(withDoc: QueryDocumentSnapshot) {
        self.wine_id = withDoc.documentID
        self.name = withDoc.get("name") as? String ?? "no name"
        self.rating = withDoc.get("rating") as? String ?? "no rating"
        self.state = withDoc.get("state") as? String ?? "no state"
    }

    func updateProperties(withDoc: QueryDocumentSnapshot) {
        self.name = withDoc.get("name") as? String ?? "no name"
        self.rating = withDoc.get("rating") as? String ?? "no rating"
        self.state = withDoc.get("state") as? String ?? "no state"
    }
}

最后是一个类 var Array,它将充当 tableView 或 collectionView 的数据源。

var wineArray = [WineClass]()

然后是代码 - 我们只对来自 CA 的葡萄酒感兴趣,因此查询将结果限制为仅适用于本示例的那些。

func handleSpecificChanges() {
    let collectionRef = self.db.collection("wines")
    collectionRef.whereField("state", isEqualTo: "CA").addSnapshotListener { querySnapshot, error in
        guard let snapshot = querySnapshot else {
            print("Error fetching snapshots: \(error!)")
            return
        }

        snapshot.documentChanges.forEach { diff in
            if (diff.type == .added) {
                let wineToAdd = WineClass(withDoc: diff.document)
                self.wineArray.append(wineToAdd)
                print("added: \(wineToAdd.wine_id)  \(wineToAdd.name) \(wineToAdd.rating)  \(wineToAdd.state)")
            }
            if (diff.type == .modified) {
                let docId = diff.document.documentID
                if let indexOfWineToModify = self.wineArray.firstIndex(where: { $0.wine_id == docId} ) {
                    let wineToModify = self.wineArray[indexOfWineToModify]
                    wineToModify.updateProperties(withDoc: diff.document)
                    print("modified: \(wineToModify.wine_id)  \(wineToModify.name) \(wineToModify.rating)  \(wineToModify.state)")
                }
            }
            if (diff.type == .removed) {
                let docId = diff.document.documentID
                if let indexOfWineToRemove = self.wineArray.firstIndex(where: { $0.wine_id == docId} ) {
                    self.wineArray.remove(at: indexOfWineToRemove)
                    print("removed: \(docId)")
                }
            }
        }
    }
}

一些笔记

docId (doc_0, doc_1) 等是将它们结合在一起的关键。它保证是唯一的,您可以通过它知道哪些酒已被修改或删除,并在 Firestore 中创建酒时分配。

当此代码最初运行时,会为每个匹配查询的葡萄酒调用 . added。这允许您最初使用匹配项填充您的 dataSource 数组,然后将监视添加的事件。

您的一个问题是更新对象的字段而不完全覆盖它。这可能不是很重要 - 正如您从修改事件中看到的那样,我们只需将当前字段传递给对象以更新所有字段。如果您真的不想“覆盖”字段,您可以添加一个 if 检查以查看传入的字段数据是否与原来的不同。

if self.rating != newRatnig {
   self.rating == newRating
}

但同样,这可能没有必要。

我认为这解决了问题中的所有问题。

【讨论】:

  • 感谢您非常全面的回复。我想我现在明白这些东西是如何工作的更好了。我想在一天结束的时候,我正在努力应对事物的快速方面,而不是 Firestore 本身。一旦我参加了 Pluralsight 上的这门课程,事情就真正开始了:(app.pluralsight.com/library/courses/swift3-fundamentals/…) 对于正在努力使用 Swift 语言的初学者,我不能再推荐这门课程了。值得每一分钱。
猜你喜欢
  • 2018-11-12
  • 2018-09-18
  • 1970-01-01
  • 2020-02-21
  • 2020-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-01
相关资源
最近更新 更多