Các ứng dụng Firebase vẫn 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ý sự hiện diện và xử lý độ trễ.
Ổ đĩa lưu trữ dài lâu
Các ứng dụng Firebase tự động xử lý các sự cố gián đoạn mạng tạm thời. Dữ liệu đã lưu vào bộ nhớ đệm chỉ dùng được khi không có mạng và Firebase sẽ gửi lại mọi lượt 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ữ liên tục trên ổ đĩa, ứng dụng sẽ ghi dữ liệu cục bộ vào thiết bị để có thể duy trì trạng thái khi không có kết nối 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ữ liên tục trên ổ đĩa chỉ bằng một dòng mã.
Swift
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
Hành vi liên tục
Bằng cách bật tính năng ổn định, mọi dữ liệu mà ứng dụng Firebase Realtime Database sẽ đồng bộ hoá khi trực tuyến sẽ được lưu vào ổ đĩ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 trực tuyến bằng cách sử dụng dữ liệu cục bộ được lưu trữ trong bộ nhớ đệm. Lệnh gọi lại 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 Firebase Realtime Database tự động giữ 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ó kết nối mạng. Khi bạn bật chế độ liên tục, hàng đợi này cũng được lưu vào ổ đĩa để tất cả các hoạt động 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 khôi phục kết nố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, thì ứng dụng Firebase Realtime Database sẽ duy trì mã thông báo xác thực của người dùng trong quá trình 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 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 các quy tắc bảo mật.
Giữ dữ liệu mới
Firebase Realtime Database đồng bộ hoá và lưu trữ bản sao cục bộ của dữ liệu cho trình nghe đang hoạt động. Ngoài ra, bạn có thể đồng bộ hoá các vị trí cụ thể.
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
Ứng dụng Firebase Realtime Database tự động tải dữ liệu xuống tại các vị trí này và đồng bộ hoá dữ liệu ngay cả khi tệp tham chiếu không có trình nghe nào đang hoạt động. Bạn có thể tắt tính năng đồng bộ hoá bằng dòng mã sau.
Swift
scoresRef.keepSynced(false)
Objective-C
[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 là đủ đối với 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, Firebase Realtime Database sẽ xoá dữ liệu được sử dụng ít nhất gần đây. Dữ liệu được đồng bộ hoá sẽ không bị xoá khỏi bộ nhớ đệm.
Truy vấn dữ liệu ngoại tuyến
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 sẽ tiếp tục hoạt động cho dữ liệu đã tải trước đó. Nếu dữ liệu bạn 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 có kết nối mạng trở lại, dữ liệu sẽ tải và phản ánh truy vấn.
Ví dụ: đoạn mã này truy vấn 4 mục cuối cùng trong Firebase Realtime Database điểm số
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); }];
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. Mặc dù vẫn ở chế độ ngoại tuyến, nhưng ứng dụng sẽ truy vấn hai 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 cuối cùng vì ứng dụng đã tải cả 4 mục trong truy vấn trên.
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); }];
Trong ví dụ trước, ứng dụng Firebase Realtime Database sẽ tạo sự kiện "thêm con" cho hai con khủng long có điểm số cao nhất bằng cách sử dụng bộ nhớ đệm ổn định. Tuy nhiên, ứng dụng sẽ không tạo sự kiện "value" (giá trị) vì ứng dụng chưa bao giờ thực thi truy vấn đó khi có kết nối mạng.
Nếu yêu cầu 6 mục cuối cùng khi không có kết nối mạng, thì ứng dụng sẽ nhận được ngay các sự kiện "đã thêm con" cho 4 mục được lưu vào bộ nhớ đệm. Khi thiết bị kết nối lại mạng, ứng dụng khách Firebase Realtime Database sẽ đồng bộ hoá với máy chủ và nhận được hai sự kiện "thêm thành phần con" và "giá trị" cuối cùng cho ứng dụng.
Xử lý giao dịch ngoại tuyến
Mọi giao dịch được thực hiện khi ứng dụng không có mạng đều được đưa vào hàng đợi. Sau khi ứng dụng khôi phục kết nối mạng, các giao dịch sẽ được gửi đến máy chủ Realtime Database.
Quản lý sự 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 kết nối và ngắt kết nối thường sẽ 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 của họ ngắt kết nối.
Ứng dụng Cơ sở dữ liệu Firebase cung cấp các dữ liệu gốc đơn giản mà bạn có thể sử dụng để ghi vào cơ sở dữ liệu khi ứng dụng ngắt kết nối với máy chủ Cơ sở dữ liệu Firebase. 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 sạch 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ị ngắt 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 thiết lập, cập nhật và xoá, khi bị ngắt kết nối.
Dưới đây là 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 nguyên hàm 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!"];
Cách bật tính năng ngắt kết nối hoạt động
Khi bạn thiết lập một thao tác onDisconnect()
, thao tác đó sẽ có trên máy chủ Firebase Realtime Database. Máy chủ kiểm tra bảo mật để đảm bảo người dùng có thể thực hiện sự kiện ghi đã 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 Realtime Database chủ động đóng, thì máy chủ sẽ kiểm tra 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
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); } }];
Bạn cũng có thể huỷ sự kiện onDisconnect
bằng cách gọi .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];
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 sự hiện diện, ứng dụng của bạn cần biết khi nào ứng dụng trực tuyến hoặc ngoại tuyến. 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 Firebase Realtime Database thay đổi. Sau đây là ví dụ:
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
là một giá trị boolean không được đồng bộ hoá giữa các ứng dụng Realtime Database vì giá trị này phụ thuộc vào trạng thái của ứng dụng. Nói cách khác, nếu một ứng dụng đọc /.info/connected
là false, thì điều này không đảm bảo rằng một ứng dụng riêng biệt cũng sẽ đọc giá trị false.
Xử lý độ trễ
Dấu thời gian của máy chủ
Máy chủ Firebase Realtime Database cung cấp 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 đáng tin cậy thời điểm ứng dụng khách Realtime Database bị ngắt kết nối:
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]];
Độ lệch xung nhịp
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ể sử dụng để ước tính độ lệch đồng hồ của ứng dụng so với máy chủ của Firebase Realtime Database. Bạn có thể đính kèm một lệnh gọi lại vào vị trí /.info/serverTimeOffset
để lấy giá trị (tính bằng mili giây) mà ứng dụng Firebase Realtime Database thêm vào thời gian được báo cáo cục bộ (thời gian bắt đầu của hệ thống tính bằng mili giây) để ước tính thời gian máy chủ. Xin lưu ý rằng độ chính xác của độ lệch này có thể chịu ảnh hưởng của độ trễ mạng, vì vậy, độ lệch này chủ yếu hữu ích để phát hiện sự chênh lệch lớn (> 1 giây) về thời gian đồng hồ.
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); }];
Ứng dụng mẫu về trạng thái hiện diện
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 máy chủ, bạn có thể xây dựng một hệ thống 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 Realtime Database có trực tuyến hay không. Máy khách đặt vị trí này thành true khi họ kết nối mạng và dấu thời gian khi họ ngắt kết nối. Dấu thời gian này cho biết lần gần nhất người dùng đó trực tuyến.
Xin lưu ý rằng ứng dụng của bạn phải đưa các thao tác ngắt kết nối vào hàng đợi trước khi người dùng được đánh dấu là trực tuyến, để tránh mọi điều kiện tương tranh trong trường hợp kết nối mạng của ứng dụng 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 hiện diện người dùng đơn giản:
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 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
// 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]]; } }];