全新推出 Cloud Firestore(测试版):试用 Firebase 和 Google Cloud Platform 全新推出的规模可扩展且灵活的数据库。详细了解 Cloud Firestore

iOS 上的离线功能

即使暂时失去网络连接,Firebase 应用仍可正常工作。此外,Firebase 还提供用于本地保存数据、管理在线状态和处理延时的工具。

磁盘持久化

Firebase 应用会自动处理临时性网络中断。缓存的数据在离线状态下依然可用,而且系统会在网络连接恢复后重新发送写入操作。

在启用磁盘持久化后,您的应用会向设备本地写入数据,从而使您的应用可以在离线时维持状态,即使在用户或操作系统重新启动应用时也是如此。

您只需一行代码,便可启用磁盘持久化。

Swift

Database.database().isPersistenceEnabled = true

Objective-C

[FIRDatabase database].persistenceEnabled = YES;

持久化行为

启用持久化后,Firebase 实时数据库客户端在联网状态下同步的所有数据都会持久保存在磁盘中,且可在离线状态下使用,即使在用户或操作系统重新启动应用后也是如此。也就是说,您的应用会使用存储在缓存中的本地数据,如同在线时一样正常运行。本地更新仍会触发侦听器回调。

Firebase 实时数据库客户端会自动保留应用处于离线状态时所执行的所有写入操作的队列。启用持久化后,这个队列也会持久保存在磁盘中,因此即使在用户或操作系统重新启动此应用后,您的写入操作也不会丢失。当应用恢复在线状态时,系统会将所有操作发送到 Firebase 实时数据库服务器。

如果应用使用 Firebase 身份验证,Firebase 实时数据库客户端还会持久保留用户的身份验证令牌,即使应用重启也不受影响。如果身份验证令牌在应用处于离线状态时到期,则客户端会暂停写入操作,直到您的应用重新进行身份验证,否则写入操作可能会由于安全规则而失败。

及时更新数据

Firebase 实时数据库会同步数据并存储一份本地副本,以供处于活动状态的侦听器使用。此外,您还可以使特定位置中的数据保持同步。

Swift

let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Firebase 实时数据库客户端会自动下载这些位置中的数据并让其保持同步,即使引用中没有处于活动状态的侦听器也是如此。您可以使用下面这行代码停止同步。

Swift

scoresRef.keepSynced(false)

Objective-C

[scoresRef keepSynced:NO];

默认情况下,之前同步的数据中有 10MB 会被写入缓存。这对大多数应用来说应该足够了。如果缓存数据的大小超过其配置的大小,则 Firebase 实时数据库会清除最近最少使用的数据。保持同步的数据不会从缓存中清除。

离线查询数据

Firebase 实时数据库会存储查询所返回的数据,供您在离线状态下使用。对于在离线状态下构建的查询,Firebase 实时数据库会继续处理之前加载的数据。如果请求的数据还未加载,则 Firebase 实时数据库会从本地缓存中加载数据。在恢复网络连接后,系统会加载数据并给出查询结果。

例如,下列代码用于查询 Firebase 实时数据库中关于分数的最后四项内容:

Swift

let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[[[scoresRef queryOrderedByValue] queryLimitedToLast:4]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

假设用户连接中断,进入离线状态,然后重启了应用。在仍处于离线状态时,此应用从同一位置对最后两条记录执行了查询。此查询会成功返回最后两条记录,因为应用已经加载过上述查询中的所有四条记录。

Swift

scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

在上述示例中,Firebase 实时数据库客户端通过使用持久化缓存,针对“分数最高的两种恐龙”查询引发了“child added”事件。但它不会引发“value”事件,因为该应用在处于在线状态时未执行过该查询。

如果应用要在离线状态下查询最后六条记录,则对于已缓存的四条记录,它会立即为其引发“child added”事件。设备恢复在线状态后,Firebase 实时数据库客户端会与服务器同步,并为应用获得剩下两项内容对应的“child added”事件以及“value”事件。

离线处理事务

系统会将在应用处于离线状态时所执行的所有事务放入队列中。当应用恢复在线状态后,系统会将事务发送到 Firebase 实时数据库服务器。

管理在线状态

在实时应用中,通常需要通过检测得知客户端连接和断开连接的时间。例如,当用户的客户端断开连接时,您可能需要将该用户标记为“离线”。

Firebase 数据库客户端提供了简单的基本方法,便于您在客户端与 Firebase 数据库服务器断开连接时将数据写入数据库。这些更新的发生与客户端是否正常断开连接无关,因此,即使突然断开连接或客户端崩溃,您仍可以依靠此类更新来清理数据。所有写入操作(包括设置、更新和移除)均可以在断开连接时执行。

在下面这个简单的示例中,我们使用 onDisconnect 基本方法在断开连接时写入数据:

Swift

let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objective-C

FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

onDisconnect 的工作方式

当您建立 onDisconnect() 操作后,它会在 Firebase 实时数据库服务器上驻留。该服务器会检查安全性,确保用户可以执行所请求的写入事件,并在该操作无效时通知您的应用。然后,该服务器会监控连接状况。如果在任何时间点连接超时,或实时数据库客户端主动关闭连接,则该服务器会第二次检查安全性(以确保操作仍有效),然后触发事件。

您的应用可以在写入操作中使用回调,以确保正确附加 onDisconnect

Swift

presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objective-C

[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

onDisconnect 事件还可以通过调用 .cancel() 来取消:

Swift

presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

检测连接状态

对于许多与在线状态相关的功能,让您的应用了解自己处于在线还是离线状态非常有用。Firebase 实时数据库在 /.info/connected 这个特殊位置中提供了相关信息(每当 Firebase 实时数据库客户端的连接状态发生变化时便会更新)。示例如下:

Swift

let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

Objective-C

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    NSLog(@"connected");
  } else {
    NSLog(@"not connected");
  }
}];

/.info/connected 是一个未在实时数据库客户端之间同步的布尔值,因为该值取决于客户端的状态。换句话说,一个客户端将 /.info/connected 读取为 false,并不能保证另一个客户端也会将其读取为 false。

应对延迟

服务器时间戳

Firebase 实时数据库服务器提供的机制可以以数据形式插入服务器上生成的时间戳。这项功能与 onDisconnect 相结合,可让您轻松可靠地记录实时数据库客户端断开连接的时间:

Swift

let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objective-C

FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

时钟偏差

虽然 firebase.database.ServerValue.TIMESTAMP 对大多数读/写操作来说较为准确和适用,但有时估算客户端相较于 Firebase 实时数据库服务器的时钟偏差还是有其作用。您可以在 /.info/serverTimeOffset 这个位置附加回调来获取相应值(以毫秒为单位),Firebase 实时数据库客户端会将这个值与本地报告的时间(以毫秒为单位的 Unix 时间)相加,来估算服务器时间。请注意,这个时间差的准确性可能会受到网络延迟的影响,因此,它主要用于发现较长(> 1 秒)的时钟时间差异。

Swift

let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)")
  }
})

Objective-C

FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"];
[offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue];
  NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset;
  NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs);
}];

在线状态应用示例

通过将断开连接操作与连接状态监控和服务器时间戳相结合,您可以建立一个用户在线状态系统。在这个系统中,每位用户都可以通过在数据库中的某个位置存储数据,来表明其实时数据库客户端是否在线。客户端在自己处于在线状态时将这个位置设为 true,并记录自己断开连接时的时间戳。这个时间戳显示的是指定用户上次在线的时间。

请注意,将用户标记为在线之前,您的应用应将断开连接操作放入队列,以免在因客户端网络连接中断而无法将两个命令同时发送到服务器的情况下,出现争用条件。

下方是一个简单的用户在线状态系统:

Swift

// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard let connected = snapshot.value as? Bool, connected else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

Objective-C

// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"];

// stores the timestamp of my last disconnect (the last time I was seen online)
FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    // connection established (or I've reconnected after a loss of connection)

    // add this device to my connections list
    FIRDatabaseReference *con = [myConnectionsRef childByAutoId];

    // when this device disconnects, remove it
    [con onDisconnectRemoveValue];

    // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
    // where you set the user's presence to true and the client disconnects before the
    // onDisconnect() operation takes effect, leaving a ghost user.

    // this value could contain info about the device or a timestamp instead of just true
    [con setValue:@YES];

    // when I disconnect, update the last time I was seen online
    [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
  }
}];

发送以下问题的反馈:

此网页
Firebase 实时数据库
需要帮助?请访问我们的支持页面