แอปพลิเคชัน Firebase จะทำงานได้แม้ว่าแอปจะขาดการเชื่อมต่อเครือข่ายชั่วคราว นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับเก็บข้อมูลไว้ในเครื่อง จัดการสถานะ และจัดการเวลาในการตอบสนองด้วย
ความต่อเนื่องของดิสก์
แอป Firebase จะจัดการกับการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลที่แคชไว้จะพร้อมใช้งานขณะออฟไลน์ และ Firebase จะส่งการเขียนอีกครั้งเมื่อการเชื่อมต่อเครือข่ายกลับมาเป็นปกติ
เมื่อเปิดใช้การคงอยู่ของดิสก์ แอปจะเขียนข้อมูลไว้ในอุปกรณ์เพื่อให้แอปคงสถานะไว้ได้ขณะออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม
คุณเปิดใช้ความต่อเนื่องของดิสก์ได้ด้วยโค้ดเพียงบรรทัดเดียว
Swift
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
พฤติกรรมความต่อเนื่อง
เมื่อเปิดใช้ความต่อเนื่อง ข้อมูลที่ไคลเอ็นต์ Firebase Realtime Database จะซิงค์ขณะออนไลน์จะยังคงอยู่ในดิสก์และพร้อมใช้งานแบบออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม ซึ่งหมายความว่าแอปจะทำงานเหมือนกับตอนออนไลน์โดยใช้ข้อมูลในพื้นที่ที่จัดเก็บไว้ในแคช ระบบจะยังคงเรียกใช้ Callback ของ Listener สำหรับการอัปเดตในเครื่อง
ไคลเอ็นต์ Firebase Realtime Database จะเก็บคิวของการดำเนินการเขียนทั้งหมดที่ทำขณะที่แอปออฟไลน์โดยอัตโนมัติ เมื่อเปิดใช้การคงอยู่ คิวนี้จะคงอยู่ในดิสก์ด้วยเพื่อให้การเขียนทั้งหมดพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการรีสตาร์ทแอป เมื่อแอปเชื่อมต่อได้อีกครั้ง ระบบจะส่งการดำเนินการทั้งหมดไปยังเซิร์ฟเวอร์ Firebase Realtime Database
หากแอปใช้การตรวจสอบสิทธิ์ Firebase ไคลเอ็นต์ Firebase Realtime Database จะเก็บโทเค็นการตรวจสอบสิทธิ์ของผู้ใช้ไว้ตลอดการรีสตาร์ทแอป หากโทเค็นการตรวจสอบสิทธิ์หมดอายุขณะที่แอปออฟไลน์ ไคลเอ็นต์จะหยุดการดำเนินการเขียนชั่วคราวจนกว่าแอปจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง มิฉะนั้นการดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย
การอัปเดตข้อมูลอยู่เสมอ
Firebase Realtime Database จะซิงค์และจัดเก็บสำเนาข้อมูลในเครื่องสำหรับ Listener ที่ใช้งานอยู่ นอกจากนี้ คุณยังซิงค์สถานที่ตั้งที่เฉพาะเจาะจงได้ด้วย
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
ไคลเอ็นต์ Firebase Realtime Database จะดาวน์โหลดข้อมูลที่ตำแหน่งเหล่านี้โดยอัตโนมัติและซิงค์ข้อมูลให้ตรงกันแม้ว่าการอ้างอิงจะไม่มี Listener ที่ทำงานอยู่ก็ตาม คุณสามารถปิดการซิงค์ข้อมูลอีกครั้งได้ด้วยโค้ดบรรทัดต่อไปนี้
Swift
scoresRef.keepSynced(false)
Objective-C
[scoresRef keepSynced:NO];
โดยค่าเริ่มต้น ระบบจะแคชข้อมูลที่ซิงค์ไว้ก่อนหน้านี้ 10 MB ซึ่งน่าจะเพียงพอสำหรับแอปพลิเคชันส่วนใหญ่ หากแคชมีขนาดเกินกว่าที่กำหนดค่าไว้ Firebase Realtime Database จะล้างข้อมูลที่ใช้ล่าสุดน้อยที่สุด ระบบจะไม่ลบข้อมูลที่เก็บไว้ในซิงค์ออกจากแคช
การค้นหาข้อมูลแบบออฟไลน์
Firebase Realtime Database จะจัดเก็บข้อมูลที่แสดงผลจากการค้นหาเพื่อใช้เมื่อออฟไลน์ สำหรับการค้นหาที่สร้างขึ้นขณะออฟไลน์ Firebase Realtime Database จะยังคงทำงานกับข้อมูลที่โหลดไว้ก่อนหน้านี้ต่อไป หากข้อมูลที่ขอไม่โหลด Firebase Realtime Database จะโหลดข้อมูลจากแคชในเครื่อง เมื่อการเชื่อมต่อเครือข่ายกลับมาใช้งานได้อีกครั้ง ข้อมูลจะโหลดและแสดงผลการค้นหา
ตัวอย่างเช่น โค้ดนี้จะค้นหารายการ 4 รายการสุดท้ายใน Firebase Realtime Database ของคะแนน
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); }];
สมมติว่าผู้ใช้ขาดการเชื่อมต่อ ออฟไลน์ และรีสตาร์ทแอป ขณะที่ยังออฟไลน์อยู่ แอปจะค้นหารายการ 2 รายการล่าสุดจากตำแหน่งเดียวกัน การค้นหานี้จะแสดงผล 2 รายการสุดท้ายได้สำเร็จเนื่องจากแอปได้โหลดทั้ง 4 รายการในการค้นหาด้านบนแล้ว
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 Realtime Database จะเพิ่มเหตุการณ์ "เพิ่มรายการย่อย" สำหรับไดโนเสาร์ 2 ตัวที่มีคะแนนสูงสุดโดยใช้แคชที่เก็บไว้ แต่จะไม่เพิ่มเหตุการณ์ "value" เนื่องจากแอปไม่เคยเรียกใช้การค้นหานั้นขณะออนไลน์
หากแอปขอรายการ 6 รายการล่าสุดขณะออฟไลน์ แอปจะได้รับเหตุการณ์ "เพิ่มรายการย่อย" สำหรับรายการที่แคชไว้ 4 รายการทันที เมื่ออุปกรณ์กลับมาออนไลน์ ไคลเอ็นต์ Firebase Realtime Database จะซิงค์กับเซิร์ฟเวอร์และรับเหตุการณ์ "เพิ่มรายการย่อย" 2 รายการสุดท้ายและเหตุการณ์ "ค่า" สำหรับแอป
การจัดการธุรกรรมแบบออฟไลน์
ธุรกรรมที่ดำเนินการขณะที่แอปออฟไลน์จะอยู่ในคิว เมื่อแอปเชื่อมต่อเครือข่ายได้อีกครั้ง ระบบจะส่งธุรกรรมไปยังเซิร์ฟเวอร์ Realtime Database
การจัดการการตรวจหาบุคคลในบ้าน
ในแอปพลิเคชันแบบเรียลไทม์ การตรวจจับเมื่อไคลเอ็นต์เชื่อมต่อและยกเลิกการเชื่อมต่อมักจะมีประโยชน์ เช่น คุณอาจต้องการทำเครื่องหมายผู้ใช้เป็น "ออฟไลน์" เมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อ
ไคลเอ็นต์ Firebase Database มีองค์ประกอบพื้นฐานที่เรียบง่ายซึ่งคุณสามารถใช้เพื่อเขียนลงในฐานข้อมูลเมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อจากเซิร์ฟเวอร์ Firebase Database การอัปเดตเหล่านี้จะเกิดขึ้นไม่ว่าไคลเอ็นต์จะยกเลิกการเชื่อมต่ออย่างสมบูรณ์หรือไม่ก็ตาม คุณจึงวางใจได้ว่าระบบจะล้างข้อมูลแม้ว่าการเชื่อมต่อจะหลุดหรือไคลเอ็นต์ขัดข้อง การดำเนินการเขียนทั้งหมด ซึ่งรวมถึงการตั้งค่า การอัปเดต และการนำออก จะดำเนินการได้เมื่อมีการยกเลิกการเชื่อมต่อ
ต่อไปนี้เป็นตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อมีการตัดการเชื่อมต่อโดยใช้ 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 Realtime Database เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้สามารถดำเนินการเขียนเหตุการณ์ที่ขอได้ และแจ้งให้แอปทราบหากไม่ถูกต้อง จากนั้นเซิร์ฟเวอร์จะตรวจสอบการเชื่อมต่อ หากการเชื่อมต่อหมดเวลาหรือถูกปิดโดยไคลเอ็นต์ Realtime Database เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเป็นครั้งที่ 2 (เพื่อให้แน่ใจว่าการดำเนินการยังคงถูกต้อง) แล้วจึงเรียกใช้เหตุการณ์
แอปของคุณสามารถใช้ Callback ในการดำเนินการเขียนเพื่อให้แน่ใจว่าแนบ 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 Realtime Database มีตำแหน่งพิเศษที่ /.info/connected ซึ่งจะอัปเดตทุกครั้งที่สถานะการเชื่อมต่อของไคลเอ็นต์ Firebase Realtime Database มีการเปลี่ยนแปลง ตัวอย่างเช่น
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 เป็นค่าบูลีนที่ไม่ได้ซิงค์ระหว่างไคลเอ็นต์ Realtime Database เนื่องจากค่าขึ้นอยู่กับสถานะของไคลเอ็นต์ กล่าวคือ หากไคลเอ็นต์หนึ่งอ่าน /.info/connected เป็นเท็จ ก็ไม่ได้รับประกันว่าไคลเอ็นต์อื่นจะอ่านเป็นเท็จด้วย
เวลาในการตอบสนองของการจัดการ
การประทับเวลาของเซิร์ฟเวอร์
เซิร์ฟเวอร์ Firebase Realtime Database มีกลไกในการแทรกการประทับเวลาที่สร้างขึ้นบนเซิร์ฟเวอร์เป็นข้อมูล ฟีเจอร์นี้เมื่อใช้ร่วมกับ onDisconnect จะช่วยให้คุณจดบันทึกเวลาที่ไคลเอ็นต์ Realtime Database ยกเลิกการเชื่อมต่อได้อย่างน่าเชื่อถือ
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 Realtime Database ก็มีประโยชน์ คุณสามารถแนบ Callback ไปยังตำแหน่ง /.info/serverTimeOffset เพื่อรับค่าเป็นมิลลิวินาทีที่ไคลเอ็นต์ Firebase Realtime Database เพิ่มลงในเวลาที่รายงานในเครื่อง (เวลา Epoch เป็นมิลลิวินาที) เพื่อประมาณเวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของค่าออฟเซ็ตนี้อาจได้รับผลกระทบจากเวลาในการตอบสนองของเครือข่าย ดังนั้นจึงมีประโยชน์เป็นหลักในการค้นหาความคลาดเคลื่อนของเวลาในนาฬิกาที่มีขนาดใหญ่ (> 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); }];
แอปตัวอย่างการตรวจหาบุคคล
เมื่อรวมการดำเนินการยกเลิกการเชื่อมต่อกับการตรวจสอบสถานะการเชื่อมต่อและการประทับเวลาของเซิร์ฟเวอร์ คุณจะสร้างระบบสถานะของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละรายจะจัดเก็บข้อมูลไว้ในตำแหน่งฐานข้อมูลเพื่อระบุว่าไคลเอ็นต์ Realtime Database ออนไลน์อยู่หรือไม่ ไคลเอ็นต์จะตั้งค่าตำแหน่งนี้เป็น "จริง" เมื่อออนไลน์ และตั้งค่าการประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้จะระบุเวลาล่าสุดที่ผู้ใช้รายนั้นออนไลน์
โปรดทราบว่าแอปควรจัดคิวการดำเนินการยกเลิกการเชื่อมต่อก่อนที่จะทำเครื่องหมายผู้ใช้เป็นออนไลน์ เพื่อหลีกเลี่ยงภาวะแข่งขันในกรณีที่การเชื่อมต่อเครือข่ายของไคลเอ็นต์ขาดหายไปก่อนที่จะส่งทั้ง 2 คำสั่งไปยังเซิร์ฟเวอร์ได้
ตัวอย่างระบบการแสดงข้อมูลของผู้ใช้แบบง่ายๆ มีดังนี้
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]]; } }];