移动编码人员多年来一直在利用 Google 的 移动后端即服务 (MBaaS) 平台 firebase 实时数据库 ,帮助他们专注于为其应用程序构建功能,而不必担心后端基础架构和数据库。通过简化在云中存储和持久化数据以及处理身份验证和安全性,Firebase 允许编码人员专注于客户端。
去年,Google 宣布了另一个后端数据库解决方案 Cloud Firestore,它从头开始构建,承诺提供更高的可扩展性和直观性。然而,这给它与谷歌已经存在的旗舰产品 Firebase Realtime database的关系带来了一些混乱。本教程将概述两个平台之间的差异以及每个平台的独特优势。通过构建一个简单的提醒应用程序,您将学习如何使用 Fi rest ore 文档引用,以及实时读取、写入、更新和删除数据。
本教程的目标
本教程将向您展示 Cloud Firestore。您将学习如何利用该平台进行实时数据库持久性和同步。我们将涵盖以下主题:
什么是 Cloud Firestore
Firestore 数据模型
设置 Cloud Firestore
创建和使用 Cloud Firestore 引用
从 Cloud Firestore 实时读取数据
创建、更新和删除数据
过滤和复合查询
假设知识
本教程假设您已经接触过 Firebase 并具有使用 swift 和 Xcode 进行开发的背景。
什么是 Cloud Firestore?
与 Firebase 实时数据库一样,Firestore 为移动和 Web 开发人员提供了跨平台的云解决方案,以实时保存数据,不受网络延迟或互联网连接的影响,并与 Google Cloud Platform 产品套件无缝集成。除了这些相似之处之外,还有一些明显的优点和缺点可以将两者区分开来。
数据模型
在基本层面上,实时数据库将数据存储为一个大型的、单一的、分层的 JSON 树,而 Firestore 将数据组织在文档和集合以及子集合中。这需要较少的非规范化。在处理简单的数据需求时,将数据存储在一个 JSON 树中具有简单的好处;然而,当处理更复杂的分层数据时,它在规模上变得更加麻烦。
离线支持
两种产品都提供离线支持,当有潜在网络连接或没有网络连接时,主动缓存队列中的数据——尽可能将本地更改同步回后端。除了移动应用之外,Firestore 还支持 Web 应用的离线同步,而实时数据库仅支持移动同步。
查询和交易
实时数据库仅支持有限的排序和过滤功能——您只能在单个查询中对属性级别进行排序或过滤,但不能同时进行两者。查询也很深,这意味着它们会返回一个大的结果子树。该产品仅支持需要完成回调的简单写入和事务操作。
另一方面,Firestore 引入了具有复合排序和过滤的索引查询,允许您组合操作来创建链式过滤器和排序。您还可以执行浅查询返回子集合,而不是使用实时数据库获得的整个集合。事务本质上是原子的,无论您发送批处理操作还是单个操作,事务都会自动重复直到结束。此外,实时数据库仅支持单个写入事务,而 Firestore 以原子方式提供批处理操作。
性能和可扩展性
正如您所期望的那样,实时数据库非常健壮且延迟低。但是,数据库仅限于单个区域,取决于区域可用性。另一方面,Firestore 将数据横向存储在多个区域和区域中,以确保真正的全球可用性、可扩展性和可靠性。事实上,谷歌已经承诺 Firestore 将比实时数据库更可靠。
实时数据库的另一个缺点是限制为 100,000 个并发用户(单个数据库中 100,000 个并发连接和 1,000 次写入/秒),之后您必须对数据库进行分片(将数据库拆分为多个数据库)以支持更多用户. Firestore 会自动扩展多个实例,而无需您进行干预。
Firestore 从头开始设计时就考虑到了可扩展性,它有一个新的示意图架构,可以跨多个区域复制数据,负责身份验证,并在其客户端 SDK 中处理其他与安全相关的事务。它的新数据模型比 Firebase 的更直观,更类似于其他类似的 Nosql 数据库解决方案,如mongodb,同时提供更强大的查询引擎。
安全
最后,正如您从我们之前的教程中了解的那样,实时数据库通过具有单独验证触发器的级联规则来管理安全性。这适用于 Firebase Database Rules,分别验证您的数据。另一方面,Firestore 利用 Cloud Firestore 安全规则和身份和访问管理 (IAM)提供更简单但更强大的安全模型,自动排除数据验证。
Firestore 数据模型
Firestore 是一个基于 NoSQL 文档的数据库,由文档集合组成,每个文档集合都包含数据。由于它是一个 NoSQL 数据库,因此您不会获得在关系数据库中可以找到的表、行和其他元素,而是可以在文档中找到的一组键/值对。
您通过将数据分配给文档来隐式创建文档和集合,如果文档或集合不存在,它将自动为您创建,因为集合始终必须是根(第一个)节点。这是您将很快处理的项目的简单任务示例架构,包括任务集合,以及包含两个字段的大量文档,名称(字符串)和任务是否完成的标志(布尔值) .
让我们分解每个元素,以便您更好地理解它们。
收藏品
与 SQL 世界中的数据库表同义,集合包含一个或多个文档。集合必须是架构中的根元素,并且只能包含文档,不能包含其他集合。但是,您可以引用一个文档,该文档又引用集合(子集合)。
在上面的图表中,一个任务由两个原始字段(名称和完成)以及一个由它自己的两个原始字段组成的子集合(子任务)组成。
文件
文档由键/值对组成,其值具有以下类型之一:
原始字段(例如字符串、数字、布尔值)
复杂的嵌套对象(原语列表或数组)
子系列
嵌套对象也称为映射,在文档中可以如下表示。以下分别是嵌套对象和数组的示例:
ID: 2422892 //primitive name: “Remember to buy milk” detail: //nested object notes: "This is a task to buy milk from the store" created: 2017-04-09 due: 2017-04-10 done: false notify: ["2F22-89R2", "L092-G623", "H00V-T4S1"] ...
有关支持的数据类型的更多信息,请参阅 Google 的 数据类型 文档。接下来,您将设置一个项目以使用 Cloud Firestore。
设置项目
如果您以前使用过 Firebase,那么您应该熟悉其中的很多内容。否则,您将需要在 Firebase 中创建一个帐户,并按照我们之前教程“ios 版 Firebase 身份验证入门”的“设置项目”部分中的说明进行操作 。
要学习本教程,请克隆 教程项目 repo。 接下来,通过将以下内容添加到您的 Podfile来包含 Firestore 库:
pod 'Firebase/Core' pod 'Firebase/Firestore'
在您的终端中输入以下内容以构建您的库:
pod install
接下来,切换到 Xcode 并打开 .xcworkspace 文件。导航到AppDelegate.swift 文件并在方法中输入以下内容 application:didFinishLaunchingWithOptions: :
FirebaseApp.configure()
在浏览器中,转到 Firebase 控制台 并选择左侧 的数据库选项卡
确保选择“以 测试模式启动”选项, 以便在我们进行试验时您不会遇到任何安全问题,并在您将应用程序投入生产时注意安全通知。您现在已准备好创建一个集合和一些示例文档。
添加集合和示例文档
首先Tasks,通过选择 添加收藏 按钮并命名收藏,创建一个初始收藏,如下图所示:
对于第一个文档,您将文档 ID 留空,这将自动为您生成一个 ID。该文档将仅包含两个字段: name 和 done。
保存文档,您应该能够确认收藏和文档以及自动生成的 ID:
使用云中的示例文档设置数据库后,您就可以开始在 Xcode 中实现 Firestore SDK。
创建和使用数据库引用
在 Xcode 中打开 MasterViewController.swift 文件并添加以下行来导入库:
import Firebase class MasterViewController: uitableviewController { @IBOutlet weak var addButton: UIBarButtonItem! private var documents: [DocumentSnapshot] = [] public var tasks: [Task] = [] private var listener : ListenerRegistration! ...
在这里,您只需创建一个***器变量,该变量将允许您在发生更改时实时触发与数据库的连接。您还将创建一个DocumentSnapshot包含临时数据快照的引用。
在继续使用视图控制器之前,创建另一个 swift 文件 Task.swift,它将代表您的数据模型:
import Foundation struct Task{ var name:String var done: Bool var id: String var dictionary: [String: Any] { return [ "name": name, "done": done ] } } extension Task{ init?(dictionary: [String : Any], id: String) { guard let name = dictionary["name"] as? String, let done = dictionary["done"] as? Bool else { return nil } self.init(name: name, done: done, id: id) } }
上面的代码片段包括一个方便属性(字典)和方法(init),这将使填充模型对象更容易。切换回视图控制器,并声明一个全局 setter 变量,它将基本查询约束到任务列表中的前 50 个条目。设置查询变量后,您还将删除***器,如下面的didSet属性所示:
fileprivate func baseQuery() -> Query { return Firestore.firestore().collection("Tasks").limit(to: 50) } fileprivate var query: Query? { didSet { if let listener = listener { listener.remove() } } } override func viewDidLoad() { super.viewDidLoad() self.query = baseQuery() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.listener.remove() }
从 Cloud Firestore 实时读取数据
在文档引用到位后viewWillAppear(_animated: Bool),将您之前创建的***器与查询快照的结果相关联,并检索文档列表。这是通过调用 Firestore 方法完成的 query?.addSnapshotListener:
self.listener = query?.addSnapshotListener { (documents, error) in guard let snapshot = documents else { print("Error fetching documents results: \(error!)") return } let results = snapshot.documents.map { (document) -> Task in if let task = Task(dictionary: document.data(), id: document.documentID) { return task } else { fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())") } } self.tasks = results self.documents = snapshot.documents self.tableView.reloadData() }
上面的闭包 snapshot.documents通过迭代地映射数组并将其包装到 Task 快照中每个数据项的新模型实例对象来分配 。因此,只需几行代码,您就成功地从云中读取了所有任务并将它们分配给全局tasks 数组。
要显示结果,请填充以下委托方法: TableView
override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tasks.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let item = tasks[indexPath.row] cell.textLabel!.text = item.name cell.textLabel!.textColor = item.done == false ? UIColor.black : UIColor.lightGray return cell }
在这个阶段,构建并运行项目,在模拟器中你应该能够观察到实时出现的数据。通过 Firebase 控制台添加数据,您应该会看到它立即出现在应用模拟器中。
创建、更新和删除数据
从后端成功读取内容后,接下来您将创建、更新和删除数据。下一个示例将说明如何更新数据,使用一个人为的示例,其中应用程序只允许您通过点击单元格将项目标记为已完成。注意 闭包属性,它只是引用一个特定的文档 ID,更新字典中的每个字段:collection.document(item.id).updateData(["done": !item.done])
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = tasks[indexPath.row] let collection = Firestore.firestore().collection("Tasks") collection.document(item.id).updateData([ "done": !item.done, ]) { err in if let err = err { print("Error updating document: \(err)") } else { print("Document successfully updated") } } tableView.reloadRows(at: [indexPath], with: .automatic) }
要删除项目,请调用 方法:document(item.id).delete()
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if (editingStyle == .delete){ let item = tasks[indexPath.row] _ = Firestore.firestore().collection("Tasks").document(item.id).delete() } }
创建一个新任务将涉及在 Storyboard 中添加一个新按钮并将其连接IBAction到视图控制器,创建一个addTask(_ sender:) 方法。当用户按下按钮时,它会弹出一个警告表,用户可以在其中添加新的任务名称:
collection("Tasks").addDocument (data: ["name": textFieldReminder.text ?? "empty task", "done": false])
通过输入以下内容完成应用程序的最后一部分:
@IBAction func addTask(_ sender: Any) { let alertVC : UIAlertController = UIAlertController(title: "New Task", message: "What do you want to remember?", preferredStyle: .alert) alertVC.addTextField { (UITextField) in } let cancelAction = UIAlertAction.init(title: "Cancel", style: .destructive, handler: nil) alertVC.addAction(cancelAction) //Alert action closure let addAction = UIAlertAction.init(title: "Add", style: .default) { (UIAlertAction) -> Void in let textFieldReminder = (alertVC.textFields?.first)! as UITextField let db = Firestore.firestore() var docRef: DocumentReference? = nil docRef = db.collection("Tasks").addDocument(data: [ "name": textFieldReminder.text ?? "empty task", "done": false ]) { err in if let err = err { print("Error adding document: \(err)") } else { print("Document added with ID: \(docRef!.documentID)") } } } alertVC.addAction(addAction) present(alertVC, animated: true, completion: nil) }
再次构建并运行应用程序,当模拟器出现时,尝试添加一些任务,以及将一些标记为已完成,最后通过删除一些任务来测试删除功能。您可以通过切换到 Firebase 数据库控制台并观察集合和文档来确认存储的数据已实时更新。
过滤和复合查询
到目前为止,您只使用了一个简单的查询,没有任何特定的过滤功能。要创建更健壮的查询,您可以使用whereField 子句按特定值进行过滤:
docRef.whereField(“name”, isEqualTo: searchString)
您可以使用 order(by: )和limit(to: ) 方法对查询数据进行排序和限制,如下所示:
docRef.order(by: "name").limit(5)
在 FirebaseDo 应用中,您已经使用limit了基本查询。在上面的代码片段中,您还使用了另一个功能,复合查询,其中 order 和 limit 都链接在一起。您可以根据需要链接任意数量的查询,例如在以下示例中:
docRef .whereField(“name”, isEqualTo: searchString) .whereField(“done”, isEqualTo: false) .order(by: "name") .limit(5)
结论
在本教程中,您探索了 Google 的新 MBaaS 产品 Cloud Firestore,并在此过程中创建了一个简单的任务提醒应用程序,该应用程序演示了在云中持久化、同步和查询数据是多么容易。您了解了 Firestore 与 Firebase 实时数据库相比的数据架构结构,以及如何实时读取和写入数据,以及更新和删除数据。您还学习了如何执行简单查询和复合查询,以及如何过滤数据。
- 假设知识
- 数据模型
- 离线支持
- 查询和交易
- 性能和可扩展性
- 安全
- 收藏品
- 文件
- 添加集合和示例文档
- 从 Cloud Firestore 实时读取数据
- 过滤和复合查询