获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Apple 平台上的离线功能

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

即使您的应用暂时失去网络连接,Firebase 应用也能正常工作。此外,Firebase 提供了用于在本地保存数据、管理存在和处理延迟的工具。

磁盘持久性

Firebase 应用会自动处理临时网络中断。缓存数据在离线时可用,Firebase 会在网络连接恢复时重新发送任何写入。

当您启用磁盘持久性时,您的应用程序会将数据本地写入设备,以便您的应用程序可以在离线时保持状态,即使用户或操作系统重新启动应用程序也是如此。

您只需一行代码即可启用磁盘持久性。

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
Database.database().isPersistenceEnabled = true

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
[FIRDatabase database].persistenceEnabled = YES;

持久性行为

通过启用持久性,Firebase 实时数据库客户端在联机时将同步的任何数据都会持久保存到磁盘并且可以脱机使用,即使用户或操作系统重新启动应用程序也是如此。这意味着您的应用程序可以使用存储在缓存中的本地数据像在线一样工作。侦听器回调将继续触发本地更新。

Firebase 实时数据库客户端会自动保留在您的应用离线时执行的所有写入操作的队列。启用持久性后,此队列也会持久化到磁盘,因此当用户或操作系统重新启动应用程序时,您的所有写入都可用。当应用重新连接时,所有操作都会发送到 Firebase 实时数据库服务器。

如果您的应用使用Firebase 身份验证,则 Firebase 实时数据库客户端会在应用重新启动时保留用户的身份验证令牌。如果 auth 令牌在您的应用离线时过期,客户端会暂停写入操作,直到您的应用重新对用户进行身份验证,否则写入操作可能会因安全规则而失败。

保持数据新鲜

Firebase 实时数据库为活动的侦听器同步并存储数据的本地副本。此外,您可以使特定位置保持同步。

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Firebase 实时数据库客户端会自动下载这些位置的数据并保持同步,即使引用没有活动的侦听器也是如此。您可以使用以下代码行关闭同步。

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
scoresRef.keepSynced(false)

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
[scoresRef keepSynced:NO];

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

离线查询数据

Firebase 实时数据库存储从查询返回的数据以供离线使用。对于离线构建的查询,Firebase 实时数据库会继续为之前加载的数据工作。如果请求的数据尚未加载,Firebase 实时数据库会从本地缓存加载数据。当网络连接再次可用时,数据会加载并反映查询。

例如,此代码查询 Firebase 实时分数数据库中的最后四个项目

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
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

注意:此 Firebase 产品不适用于 App Clip 目标。
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);
    }];

假设用户失去连接、下线并重新启动应用程序。虽然仍处于离线状态,但该应用会从同一位置查询最后两个项目。此查询将成功返回最后两项,因为应用程序已加载上述查询中的所有四项。

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

在前面的示例中,Firebase 实时数据库客户端通过使用持久化缓存为得分最高的两只恐龙引发“添加子”事件。但它不会引发“价值”事件,因为该应用程序从未在在线时执行过该查询。

如果应用程序在离线时请求最后六个项目,它将立即为四个缓存项目获取“添加子项”事件。当设备重新联机时,Firebase 实时数据库客户端与服务器同步并获取应用程序的最后两个“添加的子项”和“值”事件。

离线处理交易

应用离线时执行的任何事务都会排队。一旦应用程序重新获得网络连接,事务就会发送到实时数据库服务器。

管理存在

在实时应用程序中,检测客户端何时连接和断开连接通常很有用。例如,您可能希望在客户端断开连接时将用户标记为“离线”。

Firebase 数据库客户端提供简单的原语,当客户端与 Firebase 数据库服务器断开连接时,您可以使用这些原语写入数据库。无论客户端是否完全断开连接,这些更新都会发生,因此即使连接断开或客户端崩溃,您也可以依靠它们来清理数据。所有写入操作,包括设置、更新和删除,都可以在断开连接时执行。

这是一个使用onDisconnect原语在断开连接时写入数据的简单示例:

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

onDisconnect 的工作原理

当您建立onDisconnect()操作时,该操作位于 Firebase 实时数据库服务器上。服务器检查安全性以确保用户可以执行请求的写入事件,并在它无效时通知您的应用程序。然后服务器监视连接。如果在任何时候连接超时,或者被实时数据库客户端主动关闭,服务器会再次检查安全性(以确保操作仍然有效),然后调用事件。

您的应用可以在写操作上使用回调来确保正确附加了onDisconnect

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

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

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

检测连接状态

对于许多与状态相关的功能,让您的应用知道它何时在线或离线很有用。 Firebase 实时数据库在/.info/connected提供了一个特殊位置,每次 Firebase 实时数据库客户端的连接状态更改时都会更新该位置。这是一个例子:

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
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

注意:此 Firebase 产品不适用于 App Clip 目标。
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相结合,提供了一种简单的方法来可靠地记录实时数据库客户端断开连接的时间:

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objective-C

注意:此 Firebase 产品不适用于 App Clip 目标。
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

时钟偏差

虽然firebase.database.ServerValue.TIMESTAMP更准确,并且对于大多数读/写操作更可取,但有时估计客户端相对于 Firebase 实时数据库服务器的时钟偏差可能很有用。您可以将回调附加到位置/.info/serverTimeOffset以获取 Firebase 实时数据库客户端添加到本地报告时间(以毫秒为单位的纪元时间)以估计服务器时间的值(以毫秒为单位)。请注意,此偏移的准确性可能会受到网络延迟的影响,因此主要用于发现时钟时间的大(> 1 秒)差异。

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
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

注意:此 Firebase 产品不适用于 App Clip 目标。
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,并在断开连接时设置时间戳。此时间戳指示给定用户的最后一次在线时间。

请注意,您的应用应在将用户标记为在线之前将断开连接操作排队,以避免在两个命令都可以发送到服务器之前客户端的网络连接丢失的情况下出现任何竞争条件。

这是一个简单的用户存在系统:

迅速

注意:此 Firebase 产品不适用于 App Clip 目标。
// 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

注意:此 Firebase 产品不适用于 App Clip 目标。
// 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]];
  }
}];