处理数据列表(Apple 平台)

获取 FIRDatabaseReference

如需读取或写入数据库数据,您需要一个 FIRDatabaseReference 实例:

Swift

注意:此 Firebase 产品不适用于 App Clip 目标。
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

读取和写入列表

向数据列表附加数据

使用 childByAutoId 方法可将数据附加到多用户应用的列表中。每次将新的子项添加到指定的 Firebase 引用时,childByAutoId 方法均会生成一个唯一键。如果为列表中的每个新元素使用这些自动生成的键,则多个客户端可以同时向同一位置添加子项,而不会引起写入冲突。childByAutoId 生成的唯一键是以时间戳为基础的,因此列表项会自动按时间顺序排列。

您可以使用对 childByAutoId 方法返回的新数据的引用,获取子项自动生成的键值或为子项设置数据。对 childByAutoId 引用调用 getKey 将返回自动生成的键。

然后,您可以使用这些自动生成的键简化数据结构的展平过程。如需了解详情,请参阅数据扇出示例

监听子项事件

当某个节点的子项上发生特定操作时,为了响应这些操作就会触发子项事件,引发子项事件的操作包括通过 childByAutoId 方法添加新的子项,或通过 updateChildValues 方法更新子项。

事件类型 典型用法
FIRDataEventTypeChildAdded 检索项列表,或监听项列表中是否添加了新项。该事件将针对每个现有的子项触发一次,并在每次向指定的路径添加新的子项时再次触发。系统将向监听器传递一个包含新子项的数据的快照。
FIRDataEventTypeChildChanged 监听列表中的项是否发生了更改。每次修改子节点时,均会触发此事件。这包括对子节点的后代所做的任何修改。传递给事件监听器的快照包含子项的更新数据。
FIRDataEventTypeChildRemoved 监听列表中是否有项被移除。移除直接子项将会触发此事件。传递给回调块的快照包含已移除的子项的数据。
FIRDataEventTypeChildMoved 监听经过排序的列表的项顺序是否有更改。只要更新会引发子项重新排序,就会触发此事件。该事件用于已通过 queryOrderedByChild queryOrderedByValue 排序的数据。

同时使用所有这些事件有助于监听数据库中某个特定节点是否有更改。例如,社交博客应用可以结合使用这些方法来监控博文评论中的活动,如下所示:

Swift

注意:此 Firebase 产品不适用于 App Clip 目标。
// Listen for new comments in the Firebase database
commentsRef.observe(.childAdded, with: { (snapshot) -> Void in
  self.comments.append(snapshot)
  self.tableView.insertRows(
    at: [IndexPath(row: self.comments.count - 1, section: self.kSectionComments)],
    with: UITableView.RowAnimation.automatic
  )
})
// Listen for deleted comments in the Firebase database
commentsRef.observe(.childRemoved, with: { (snapshot) -> Void in
  let index = self.indexOfMessage(snapshot)
  self.comments.remove(at: index)
  self.tableView.deleteRows(
    at: [IndexPath(row: index, section: self.kSectionComments)],
    with: UITableView.RowAnimation.automatic
  )
})

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
// Listen for new comments in the Firebase database
[_commentsRef
              observeEventType:FIRDataEventTypeChildAdded
              withBlock:^(FIRDataSnapshot *snapshot) {
                [self.comments addObject:snapshot];
                [self.tableView insertRowsAtIndexPaths:@[
                  [NSIndexPath indexPathForRow:self.comments.count - 1 inSection:kSectionComments]
                ]
                                      withRowAnimation:UITableViewRowAnimationAutomatic];
              }];
// Listen for deleted comments in the Firebase database
[_commentsRef
 observeEventType:FIRDataEventTypeChildRemoved
 withBlock:^(FIRDataSnapshot *snapshot) {
   int index = [self indexOfMessage:snapshot];
   [self.comments removeObjectAtIndex:index];
   [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:kSectionComments]]
                         withRowAnimation:UITableViewRowAnimationAutomatic];
 }];

监听值事件

虽然读取数据列表的推荐做法是监听子项事件,但在有些情况下监听列表引用上的值事件也很有用。

如果将一个 FIRDataEventTypeValue 观察者附加到某个数据列表,系统会以单个 DataSnapshot 的形式返回整个数据列表,然后您可以遍历该快照来访问各个子项。

即使查询仅存在一个匹配项,该快照仍是一个列表,只是仅包含一个项。如需访问该项,您需要遍历查询结果:

Swift

注意:此 Firebase 产品不适用于 App Clip 目标。
_commentsRef.observe(.value) { snapshot in
  for child in snapshot.children {
    ...
  }
}

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
[_commentsRef
              observeEventType:FIRDataEventTypeValue
              withBlock:^(FIRDataSnapshot *snapshot) {
                // Loop over children
                NSEnumerator *children = [snapshot children];
                FIRDataSnapshot *child;
                while (child = [children nextObject]) {
                  // ...
                }
              }];

当您希望通过一次操作获取某个列表的所有子项(而不是监听额外子项添加事件)时,此模式可能会很有用。

排序和过滤数据

您可以使用 Realtime Database FIRDatabaseQuery 类来检索按键、按值或按子项的值排序的数据。您还可以对排序后的结果进行过滤,从而得到一定数量的结果或特定范围的键或值。

将数据排序

如需检索经过排序的数据,请先指定一种排序依据方法,确定如何对结果排序:

方法 用法
queryOrderedByKey 按子项键对结果排序。
queryOrderedByValue 按子项值对结果排序。
queryOrderedByChild 按指定子项键的值或嵌套子项路径对结果排序。

您每次只能使用一种排序依据方法。在同一查询中多次调用一种排序依据方法会引发错误。

以下示例演示了如何检索按博文所得星数排序的用户热门博文列表:

Swift

注意:此 Firebase 产品不适用于 App Clip 目标。
// My top posts by number of stars
let myTopPostsQuery = ref.child("user-posts").child(getUid()).queryOrdered(byChild: "starCount")

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
// My top posts by number of stars
FIRDatabaseQuery *myTopPostsQuery = [[[self.ref child:@"user-posts"]
                                      child:[super getUid]]
                                     queryOrderedByChild:@"starCount"];

此查询根据用户 ID 检索数据库路径中的用户博文,并按每篇博文获得的星数排序。这种使用 ID 作为索引键的技术称为“数据扇出”;如需了解详情,请参阅设计数据库的结构

调用 queryOrderedByChild 方法可指定对结果排序所依据的子项键。在本例中,博文按各自 "starCount" 子项的值进行排序。当您具有与下例类似的数据时,查询也可以按嵌套的子项进行排序:

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

在本例中,通过在 queryOrderedByChild 调用中指定嵌套子项的相对路径,我们可以按嵌套在 metrics 键下的值对列表元素进行排序。

Swift

注意:此 Firebase 产品不适用于 App Clip 目标。
 
let postsByMostPopular = ref.child("posts").queryOrdered(byChild: "metrics/views")

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
 
FIRDatabaseQuery *postsByMostPopular = [[ref child:@"posts"] queryOrderedByChild:@"metrics/views"];

如需详细了解如何对其他数据类型进行排序,请参阅如何对查询数据进行排序

过滤数据

如需过滤数据,您可以在构建查询时将某种限制方法或范围方法与排序依据方法结合使用。

方法 用法
queryLimitedToFirst 设置要返回的项数上限:从经过排序的结果列表开头算起。
queryLimitedToLast 设置要返回的项数上限:从经过排序的结果列表结尾算起。
queryStartingAtValue 返回大于或等于指定键或值的项,具体取决于所选的排序依据方法。
queryStartingAfterValue 返回大于指定键或值的项,具体取决于所选的排序依据方法。
queryEndingAtValue 返回小于或等于指定键或值的项,具体取决于所选的排序依据方法。
queryEndingBeforeValue 返回小于指定键或值的项,具体取决于所选的排序依据方法。
queryEqualToValue 返回等于指定键或值的项,具体取决于所选的排序依据方法。

与排序依据方法不同,您可以结合使用多种限制或范围函数。例如,您可以结合使用 queryStartingAtValuequeryEndingAtValue 方法来将查询结果限制在指定的取值范围内。

限制结果数

您可以使用 queryLimitedToFirstqueryLimitedToLast 方法来设置当运行给定回调函数时要同步的子项数上限。例如,如果使用 queryLimitedToFirst 将数量上限设为 100,则起初最多只会收到 100 次 FIRDataEventTypeChildAdded 回调。如果您在 Firebase 数据库中存储的项不到 100 个,则每个项均会触发一次 FIRDataEventTypeChildAdded 回调函数。

随着项发生更改,对于落入查询范围的项,您将收到 FIRDataEventTypeChildAdded 回调函数;对于不再属于查询范围的项,您将收到 FIRDataEventTypeChildRemoved 回调函数,因此总数始终保持为 100。

以下示例演示了示例博客应用如何检索所有用户最新的 100 篇博文:

Swift

注意:此 Firebase 产品不适用于 App Clip 目标。
// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
let recentPostsQuery = (ref?.child("posts").queryLimited(toFirst: 100))!

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
FIRDatabaseQuery *recentPostsQuery = [[self.ref child:@"posts"] queryLimitedToFirst:100];

按键或值过滤

您可以使用 queryStartingAtValuequeryStartingAfterValuequeryEndingAtValuequeryEndingBeforeValuequeryEqualToValue 为查询选择任意起点、终点和等值点。这对于将数据分页或查找其子项具有特定值的项非常有用。

如何对查询数据进行排序

本部分将介绍如何通过 FIRDatabaseQuery 类中的每种排序依据方法对数据进行排序。

queryOrderedByKey

使用 queryOrderedByKey 对数据进行排序时,系统会按照键以升序返回数据。

  1. 键可以解析为 32 位整数的子项排在最前,按升序排列。
  2. 以字符串值作为键的子项紧随其后,按字典顺序以升序排列。

queryOrderedByValue

使用 queryOrderedByValue 时,系统将按子项本身的值对其进行排序。排序标准与 queryOrderedByChild 中相同,但这里使用的是节点本身的值而非指定子项键的值。

queryOrderedByChild

使用 queryOrderedByChild 时,包含指定子项键的数据将按以下方式排序:

  1. 指定子项键的值为 nil 的子项排在最前面。
  2. 接下来是指定子项键的值为 false 的子项。如果多个子项的值均为 false,则按照键以字典顺序对它们进行排序。
  3. 接下来是指定子项键的值为 true 的子项。如果多个子项的值均为 true,则按照键以字典顺序对它们进行排序。
  4. 接下来是值为数字的子项,按升序排序。如果指定子节点的多个子项具有相同的数字值,则按照键对它们进行排序。
  5. 值为字符串的子项排列在值为数字的子项后面,并按字典顺序以升序排列。如果指定子节点的多个子项具有相同的值,则按照键以字典顺序对它们进行排序。
  6. 值为对象的子项放在最后,并按照键以字典顺序排序(升序)。

分离监听器

当您退出 ViewController 时,观察者不会自动停止同步数据。如果未妥善移除,观察者会继续将数据同步到本地内存,并保留事件处理脚本闭包中捕获的所有对象,从而造成内存泄漏。当不再需要观察者时,您可以将关联的 FIRDatabaseHandle 传递给 removeObserverWithHandle 方法,以将其移除。

将回调块添加到引用时,系统会返回 FIRDatabaseHandle。 这些句柄可用于移除回调块。

如果有多个监听器添加到了一个数据库引用,则当发生某事件时,系统会调用每一个监听器。要在该位置停止同步数据,必须通过调用 removeAllObservers 方法来移除其中的所有观察者。

对监听器调用 removeObserverWithHandleremoveAllObservers 不会自动移除在子节点上注册的监听器;您还必须跟踪这些引用或句柄才能将其移除。

后续步骤