Các chức năng ngoại tuyến trên các nền tảng của Apple

Các ứng dụng Firebase hoạt động ngay cả khi ứng dụng của bạn tạm thời mất kết nối mạng. Ngoài ra, Firebase còn cung cấp các công cụ để lưu trữ dữ liệu cục bộ, quản lý trạng thái hiện diện và xử lý độ trễ.

Khả năng lưu trữ dài lâu trên ổ đĩa

Các ứng dụng Firebase tự động xử lý các trường hợp gián đoạn mạng tạm thời. Dữ liệu được lưu vào bộ nhớ đệm sẽ có sẵn khi không có mạng và Firebase sẽ gửi lại mọi thao tác ghi khi kết nối mạng được khôi phục.

Khi bạn bật tính năng lưu trữ dài lâu trên ổ đĩa, ứng dụng sẽ ghi dữ liệu cục bộ vào thiết bị để ứng dụng có thể duy trì trạng thái khi không có mạng, ngay cả khi người dùng hoặc hệ điều hành khởi động lại ứng dụng.

Bạn có thể bật tính năng lưu trữ dài lâu trên ổ đĩa chỉ bằng một dòng mã.

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
Database.database().isPersistenceEnabled = true

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
[FIRDatabase database].persistenceEnabled = YES;

Hành vi lưu trữ dài lâu

Khi bạn bật tính năng lưu trữ dài lâu, mọi dữ liệu mà ứng dụng khách Firebase Realtime Database sẽ đồng bộ hoá khi có mạng sẽ được lưu trữ dài lâu trên ổ đĩa và có sẵn khi không có mạng, ngay cả khi người dùng hoặc hệ điều hành khởi động lại ứng dụng. Điều này có nghĩa là ứng dụng của bạn hoạt động như khi có mạng bằng cách sử dụng dữ liệu cục bộ được lưu trữ trong bộ nhớ đệm. Các lệnh gọi lại của trình nghe sẽ tiếp tục kích hoạt cho các bản cập nhật cục bộ.

Ứng dụng khách Firebase Realtime Database tự động duy trì một hàng đợi gồm tất cả các thao tác ghi được thực hiện khi ứng dụng của bạn không có mạng. Khi bật tính năng lưu trữ dài lâu, hàng đợi này cũng được lưu trữ dài lâu trên ổ đĩa để tất cả các thao tác ghi của bạn đều có sẵn khi người dùng hoặc hệ điều hành khởi động lại ứng dụng. Khi ứng dụng kết nối lại, tất cả các thao tác sẽ được gửi đến máy chủ Firebase Realtime Database.

Nếu ứng dụng của bạn sử dụng Xác thực Firebase, ứng dụng khách Firebase Realtime Database sẽ lưu trữ dài lâu mã thông báo xác thực của người dùng trong các lần khởi động lại ứng dụng. Nếu mã thông báo xác thực hết hạn trong khi ứng dụng của bạn không có mạng, thì ứng dụng khách sẽ tạm dừng các thao tác ghi cho đến khi ứng dụng xác thực lại người dùng, nếu không, các thao tác ghi có thể không thành công do quy tắc bảo mật.

Luôn cập nhật dữ liệu

Firebase Realtime Database đồng bộ hoá và lưu trữ bản sao cục bộ của dữ liệu cho các trình nghe đang hoạt động. Ngoài ra, bạn có thể duy trì trạng thái đồng bộ hoá cho các vị trí cụ thể.

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Ứng dụng khách Firebase Realtime Database tự động tải dữ liệu xuống tại các vị trí này và duy trì trạng thái đồng bộ hoá ngay cả khi tham chiếu không có trình nghe đang hoạt động. Bạn có thể tắt lại tính năng đồng bộ hoá bằng dòng mã sau.

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
scoresRef.keepSynced(false)

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
[scoresRef keepSynced:NO];

Theo mặc định, 10 MB dữ liệu đã đồng bộ hoá trước đó sẽ được lưu vào bộ nhớ đệm. Dung lượng này sẽ đủ cho hầu hết các ứng dụng. Nếu bộ nhớ đệm vượt quá kích thước đã định cấu hình, thì Firebase Realtime Database sẽ xoá dữ liệu được sử dụng gần đây nhất. Dữ liệu được duy trì trạng thái đồng bộ hoá sẽ không bị xoá khỏi bộ nhớ đệm.

Truy vấn dữ liệu khi không có mạng

Firebase Realtime Database lưu trữ dữ liệu được trả về từ một truy vấn để sử dụng khi không có mạng. Đối với các truy vấn được tạo khi không có mạng, Firebase Realtime Database tiếp tục hoạt động đối với dữ liệu đã tải trước đó. Nếu dữ liệu được yêu cầu chưa tải, thì Firebase Realtime Database sẽ tải dữ liệu từ bộ nhớ đệm cục bộ. Khi kết nối mạng có sẵn trở lại, dữ liệu sẽ tải và phản ánh truy vấn.

Ví dụ: mã này truy vấn 4 mục gần đây nhất trong Firebase Realtime Database về điểm số

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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);
    }];

Giả sử người dùng mất kết nối, chuyển sang chế độ ngoại tuyến và khởi động lại ứng dụng. Trong khi vẫn ở chế độ ngoại tuyến, ứng dụng sẽ truy vấn 2 mục gần đây nhất từ cùng một vị trí. Truy vấn này sẽ trả về thành công 2 mục gần đây nhất vì ứng dụng đã tải tất cả 4 mục trong truy vấn ở trên.

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Trong ví dụ trước, ứng dụng khách Firebase Realtime Database sẽ tạo sự kiện "đã thêm phần tử con" cho 2 con khủng long có điểm số cao nhất bằng cách sử dụng bộ nhớ đệm được lưu trữ dài lâu. Tuy nhiên, sự kiện này sẽ không tạo sự kiện 'giá trị' vì ứng dụng chưa bao giờ thực thi truy vấn đó khi có mạng.

Nếu ứng dụng yêu cầu 6 mục gần đây nhất khi không có mạng, thì ứng dụng sẽ nhận được sự kiện "đã thêm phần tử con" cho 4 mục được lưu vào bộ nhớ đệm. Khi thiết bị kết nối lại, ứng dụng khách Firebase Realtime Database sẽ đồng bộ hoá với máy chủ và nhận được 2 sự kiện "đã thêm phần tử con" cuối cùng và sự kiện "giá trị" cho ứng dụng.

Xử lý giao dịch khi không có mạng

Mọi giao dịch được thực hiện khi ứng dụng không có mạng đều được xếp vào hàng đợi. Sau khi ứng dụng kết nối lại, các giao dịch sẽ được gửi đến máy chủ Realtime Database.

Quản lý trạng thái hiện diện

Trong các ứng dụng theo thời gian thực, việc phát hiện thời điểm ứng dụng khách kết nối và ngắt kết nối thường rất hữu ích. Ví dụ: bạn có thể muốn đánh dấu người dùng là "ngoại tuyến" khi ứng dụng khách của họ ngắt kết nối.

Ứng dụng khách Firebase Database cung cấp các thành phần cơ bản đơn giản mà bạn có thể dùng để ghi vào cơ sở dữ liệu khi một ứng dụng khách ngắt kết nối khỏi máy chủ Firebase Database servers. Các bản cập nhật này xảy ra cho dù ứng dụng khách ngắt kết nối một cách sạch sẽ hay không, vì vậy bạn có thể dựa vào các bản cập nhật này để dọn dẹp dữ liệu ngay cả khi kết nối bị gián đoạn hoặc ứng dụng khách gặp sự cố. Bạn có thể thực hiện tất cả các thao tác ghi, bao gồm cả việc đặt, cập nhật và xoá khi ngắt kết nối.

Dưới đây là một ví dụ đơn giản về cách ghi dữ liệu khi ngắt kết nối bằng cách sử dụng thành phần cơ bản onDisconnect:

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

Cách hoạt động của onDisconnect

Khi bạn thiết lập một thao tác onDisconnect(), thao tác đó tồn tại trên máy chủ Firebase Realtime Database. Máy chủ kiểm tra tính bảo mật để đảm bảo người dùng có thể thực hiện sự kiện ghi được yêu cầu và thông báo cho ứng dụng của bạn nếu sự kiện đó không hợp lệ. Sau đó, máy chủ sẽ giám sát kết nối. Nếu tại bất kỳ thời điểm nào, kết nối hết thời gian chờ hoặc bị ứng dụng khách Realtime Database đóng chủ động, thì máy chủ sẽ kiểm tra tính bảo mật lần thứ hai (để đảm bảo thao tác vẫn hợp lệ) rồi gọi sự kiện.

Ứng dụng của bạn có thể sử dụng lệnh gọi lại trên thao tác ghi để đảm bảo onDisconnect được đính kèm chính xác:

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

Bạn cũng có thể huỷ sự kiện onDisconnect bằng cách gọi .cancel():

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Phát hiện trạng thái kết nối

Đối với nhiều tính năng liên quan đến trạng thái hiện diện, ứng dụng của bạn cần biết thời điểm có mạng hoặc không có mạng. Firebase Realtime Database cung cấp một vị trí đặc biệt tại /.info/connected được cập nhật mỗi khi trạng thái kết nối của ứng dụng khách Firebase Realtime Database thay đổi. Dưới đây là một ví dụ:

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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 là một giá trị boolean không được đồng bộ hoá giữa các ứng dụng khách Realtime Database vì giá trị này phụ thuộc vào trạng thái của ứng dụng khách. Nói cách khác, nếu một ứng dụng khách đọc /.info/connected là false, thì điều này không đảm bảo rằng một ứng dụng khách riêng biệt cũng sẽ đọc là false.

Xử lý độ trễ

Dấu thời gian của máy chủ

Máy chủ Firebase Realtime Database cung cấp một cơ chế để chèn dấu thời gian được tạo trên máy chủ dưới dạng dữ liệu. Tính năng này, kết hợp với onDisconnect, cung cấp một cách dễ dàng để ghi lại một cách đáng tin cậy thời điểm một ứng dụng khách Realtime Database ngắt kết nối:

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objective-C

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu App Clip.
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Độ lệch đồng hồ

Mặc dù firebase.database.ServerValue.TIMESTAMP chính xác hơn nhiều và được ưu tiên cho hầu hết các thao tác đọc/ghi, nhưng đôi khi bạn có thể ước tính độ lệch đồng hồ của ứng dụng khách so với máy chủ của Firebase Realtime Database. Bạn có thể đính kèm lệnh gọi lại vào vị trí /.info/serverTimeOffset để lấy giá trị (tính bằng mili giây) mà Firebase Realtime Database ứng dụng khách thêm vào thời gian được báo cáo cục bộ (thời gian epoch tính bằng mili giây) để ước tính thời gian của máy chủ. Xin lưu ý rằng độ chính xác của độ lệch này có thể bị ảnh hưởng bởi độ trễ mạng. Vì vậy, độ lệch này chủ yếu hữu ích cho việc phát hiện các sai lệch lớn (> 1 giây) về thời gian đồng hồ.

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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);
}];

Ứng dụng hiện diện mẫu

Bằng cách kết hợp các thao tác ngắt kết nối với tính năng giám sát trạng thái kết nối và dấu thời gian của máy chủ, bạn có thể xây dựng một hệ thống trạng thái hiện diện của người dùng. Trong hệ thống này, mỗi người dùng lưu trữ dữ liệu tại một vị trí cơ sở dữ liệu để cho biết ứng dụng khách Realtime Database có đang trực tuyến hay không. Ứng dụng khách đặt vị trí này thành true khi họ trực tuyến và dấu thời gian khi ngắt kết nối. Dấu thời gian này cho biết thời điểm gần đây nhất mà người dùng được chỉ định trực tuyến.

Xin lưu ý rằng ứng dụng của bạn nên xếp hàng đợi các thao tác ngắt kết nối trước khi người dùng được đánh dấu là trực tuyến để tránh mọi tình trạng xung đột trong trường hợp kết nối mạng của ứng dụng khách bị mất trước khi cả hai lệnh có thể được gửi đến máy chủ.

Dưới đây là một hệ thống trạng thái hiện diện đơn giản của người dùng:

Swift

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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 snapshot.value as? Bool ?? false 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

Lưu ý: Sản phẩm Firebase này không có trên mục tiêu 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]];
  }
}];