แอปพลิเคชัน Firebase ทำงานได้แม้ว่าแอปของคุณจะขาดการเชื่อมต่อเครือข่ายชั่วคราว นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับการคงอยู่ของข้อมูลในเครื่อง การจัดการการแสดงตน และการจัดการเวลาแฝง
การคงอยู่ของดิสก์
แอป Firebase จะจัดการการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลแคชพร้อมใช้งานขณะออฟไลน์ และ Firebase ส่งการเขียนอีกครั้งเมื่อการเชื่อมต่อเครือข่ายกลับคืนมา
เมื่อคุณเปิดใช้งานการคงอยู่ของดิสก์ แอปของคุณจะเขียนข้อมูลลงในอุปกรณ์เพื่อให้แอปของคุณสามารถรักษาสถานะขณะออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม
คุณสามารถเปิดใช้งานการคงอยู่ของดิสก์ด้วยโค้ดเพียงบรรทัดเดียว
สวิฟต์
Database.database().isPersistenceEnabled = true
วัตถุประสงค์-C
[FIRDatabase database].persistenceEnabled = YES;
พฤติกรรมคงอยู่
เมื่อเปิดใช้งานการคงอยู่ ข้อมูลใดๆ ที่ไคลเอ็นต์ Firebase Realtime Database จะซิงค์ในขณะที่ออนไลน์ยังคงอยู่ในดิสก์และพร้อมใช้งานแบบออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม ซึ่งหมายความว่าแอปของคุณทำงานเหมือนกับออนไลน์โดยใช้ข้อมูลในเครื่องที่จัดเก็บไว้ในแคช การโทรกลับของผู้ฟังจะยังคงทำงานต่อไปสำหรับการอัปเดตในเครื่อง
ไคลเอนต์ Firebase Realtime Database จะเก็บคิวของการดำเนินการเขียนทั้งหมดโดยอัตโนมัติในขณะที่แอปของคุณออฟไลน์ เมื่อเปิดใช้งานการคงอยู่ คิวนี้จะยังคงอยู่ในดิสก์ ดังนั้นการเขียนทั้งหมดของคุณจะพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการรีสตาร์ทแอป เมื่อแอปเชื่อมต่อได้อีกครั้ง การดำเนินการทั้งหมดจะถูกส่งไปยังเซิร์ฟเวอร์ Firebase Realtime Database
หากแอปของคุณใช้ Firebase Authentication ไคลเอนต์ Firebase Realtime Database จะยืนยันโทเค็นการตรวจสอบสิทธิ์ของผู้ใช้ตลอดการรีสตาร์ทแอป หากโทเค็นการตรวจสอบสิทธิ์หมดอายุในขณะที่แอปของคุณออฟไลน์ ไคลเอนต์จะหยุดการดำเนินการเขียนชั่วคราวจนกว่าแอปของคุณจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง มิฉะนั้น การดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย
การรักษาข้อมูลให้สดใหม่
Firebase Realtime Database ซิงโครไนซ์และจัดเก็บสำเนาข้อมูลในเครื่องสำหรับผู้ฟังที่ใช้งานอยู่ นอกจากนี้ คุณยังสามารถซิงค์ตำแหน่งเฉพาะได้
สวิฟต์
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
วัตถุประสงค์-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
ไคลเอ็นต์ฐานข้อมูลเรียลไทม์ของ Firebase จะดาวน์โหลดข้อมูลที่ตำแหน่งเหล่านี้โดยอัตโนมัติและซิงค์ข้อมูลไว้แม้ว่าข้อมูลอ้างอิงจะไม่มีผู้ฟังที่ใช้งานอยู่ก็ตาม คุณสามารถปิดการซิงโครไนซ์ด้วยบรรทัดโค้ดต่อไปนี้
สวิฟต์
scoresRef.keepSynced(false)
วัตถุประสงค์-C
[scoresRef keepSynced:NO];
ตามค่าเริ่มต้น 10MB ของข้อมูลที่ซิงค์ก่อนหน้านี้จะถูกแคชไว้ ซึ่งควรจะเพียงพอสำหรับการใช้งานส่วนใหญ่ หากแคชโตกว่าขนาดที่กำหนดค่าไว้ Firebase Realtime Database จะลบข้อมูลที่มีการใช้งานล่าสุดออก ข้อมูลที่ซิงค์ไว้จะไม่ถูกลบออกจากแคช
การสืบค้นข้อมูลออฟไลน์
Firebase Realtime Database เก็บข้อมูลที่ส่งกลับจากการสืบค้นเพื่อใช้เมื่อออฟไลน์ สำหรับข้อความค้นหาที่สร้างขึ้นขณะออฟไลน์ ฐานข้อมูลเรียลไทม์ของ Firebase จะยังคงทำงานต่อไปสำหรับข้อมูลที่โหลดไว้ก่อนหน้านี้ หากไม่ได้โหลดข้อมูลที่ร้องขอ Firebase Realtime Database จะโหลดข้อมูลจากแคชในเครื่อง เมื่อการเชื่อมต่อเครือข่ายพร้อมใช้งานอีกครั้ง ข้อมูลจะโหลดและจะแสดงข้อความค้นหา
ตัวอย่างเช่น โค้ดนี้ค้นหาสี่รายการล่าสุดในฐานข้อมูล Firebase Realtime ของคะแนน
สวิฟต์
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")") }
วัตถุประสงค์-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); }];
สมมติว่า ผู้ใช้ขาดการเชื่อมต่อ ออฟไลน์ และเริ่มแอปใหม่ ในขณะที่ยังออฟไลน์อยู่ แอปจะค้นหาสองรายการสุดท้ายจากตำแหน่งเดียวกัน ข้อความค้นหานี้จะส่งคืนรายการสองรายการสุดท้ายได้สำเร็จ เนื่องจากแอปได้โหลดรายการทั้งสี่รายการในข้อความค้นหาด้านบนแล้ว
สวิฟต์
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") }
วัตถุประสงค์-C
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) { NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value); }];
ในตัวอย่างก่อนหน้านี้ ไคลเอนต์ Firebase Realtime Database เพิ่มเหตุการณ์ 'เพิ่มลูก' สำหรับไดโนเสาร์สองตัวที่ทำคะแนนสูงสุด โดยใช้แคชที่ยังคงอยู่ แต่จะไม่เพิ่มเหตุการณ์ 'ค่า' เนื่องจากแอปไม่เคยดำเนินการค้นหานั้นขณะออนไลน์
หากแอปขอหกรายการสุดท้ายขณะออฟไลน์ แอปจะได้รับเหตุการณ์ 'เพิ่มลูก' สำหรับสี่รายการที่แคชไว้ทันที เมื่ออุปกรณ์กลับมาออนไลน์ ไคลเอนต์ Firebase Realtime Database จะซิงโครไนซ์กับเซิร์ฟเวอร์และรับเหตุการณ์ 'เพิ่มลูก' และ 'ค่า' สองรายการสุดท้ายสำหรับแอป
การจัดการธุรกรรมออฟไลน์
การทำธุรกรรมใด ๆ ที่ดำเนินการในขณะที่แอปออฟไลน์จะถูกจัดคิว เมื่อแอปเชื่อมต่อเครือข่ายได้อีกครั้ง ธุรกรรมจะถูกส่งไปยังเซิร์ฟเวอร์ฐานข้อมูลเรียลไทม์
การจัดการการแสดงตน
ในแอปพลิเคชันเรียลไทม์ การตรวจจับเมื่อไคลเอนต์เชื่อมต่อและยกเลิกการเชื่อมต่อมักมีประโยชน์ ตัวอย่างเช่น คุณอาจต้องการทำเครื่องหมายผู้ใช้ว่า 'ออฟไลน์' เมื่อไคลเอนต์ของพวกเขายกเลิกการเชื่อมต่อ
ไคลเอนต์ฐานข้อมูล Firebase จัดเตรียมพื้นฐานง่ายๆ ที่คุณสามารถใช้เพื่อเขียนไปยังฐานข้อมูลเมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อจากเซิร์ฟเวอร์ฐานข้อมูล Firebase การอัปเดตเหล่านี้จะเกิดขึ้นไม่ว่าไคลเอ็นต์จะตัดการเชื่อมต่ออย่างสมบูรณ์หรือไม่ ดังนั้นคุณจึงสามารถวางใจได้ในการล้างข้อมูลแม้ว่าการเชื่อมต่อจะหลุดหรือไคลเอ็นต์ขัดข้อง การดำเนินการเขียนทั้งหมด รวมถึงการตั้งค่า การอัพเดต และการลบ สามารถดำเนินการได้เมื่อตัดการเชื่อมต่อ
นี่คือตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อตัดการเชื่อมต่อโดยใช้ onDisconnect
ดั้งเดิม:
สวิฟต์
let presenceRef = Database.database().reference(withPath: "disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnectSetValue("I disconnected!")
วัตถุประสงค์-C
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"]; // Write a string when this client loses connection [presenceRef onDisconnectSetValue:@"I disconnected!"];
วิธีการตัดการเชื่อมต่อทำงานอย่างไร
เมื่อคุณสร้างการดำเนินการ onDisconnect()
การดำเนินการนั้นจะอยู่บนเซิร์ฟเวอร์ Firebase Realtime Database เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้สามารถดำเนินการเขียนตามที่ร้องขอ และแจ้งแอปของคุณหากแอปไม่ถูกต้อง จากนั้นเซิร์ฟเวอร์จะตรวจสอบการเชื่อมต่อ หากการเชื่อมต่อหมดเวลาหรือถูกปิดโดยไคลเอ็นต์ฐานข้อมูลเรียลไทม์ เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเป็นครั้งที่สอง (เพื่อให้แน่ใจว่าการดำเนินการยังคงถูกต้อง) จากนั้นจึงเรียกใช้เหตุการณ์
แอปของคุณสามารถใช้การเรียกกลับในการเขียนเพื่อให้แน่ใจว่า onDisconnect
ถูกแนบอย่างถูกต้อง:
สวิฟต์
presenceRef.onDisconnectRemoveValue { error, reference in if let error = error { print("Could not establish onDisconnect event: \(error)") } }
วัตถุประสงค์-C
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) { if (error != nil) { NSLog(@"Could not establish onDisconnect event: %@", error); } }];
เหตุการณ์ onDisconnect
สามารถยกเลิกได้โดยการโทร .cancel()
:
สวิฟต์
presenceRef.onDisconnectSetValue("I disconnected") // some time later when we change our minds presenceRef.cancelDisconnectOperations()
วัตถุประสงค์-C
[presenceRef onDisconnectSetValue:@"I disconnected"]; // some time later when we change our minds [presenceRef cancelDisconnectOperations];
ตรวจจับสถานะการเชื่อมต่อ
สำหรับคุณลักษณะที่เกี่ยวข้องกับการแสดงตนหลายๆ อย่าง แอปของคุณจะรู้ว่าเมื่อใดออนไลน์หรือออฟไลน์จะมีประโยชน์ Firebase Realtime Database จัดเตรียมตำแหน่งพิเศษที่ /.info/connected
ซึ่งจะอัปเดตทุกครั้งที่สถานะการเชื่อมต่อของไคลเอ็นต์ Firebase Realtime Database เปลี่ยนแปลง นี่คือตัวอย่าง:
สวิฟต์
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") } })
วัตถุประสงค์-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 ตัดการเชื่อมต่อ:
สวิฟต์
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline") userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
วัตถุประสงค์-C
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"]; [userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
นาฬิกาเอียง
แม้ว่า firebase.database.ServerValue.TIMESTAMP
จะแม่นยำกว่ามาก และเป็นที่นิยมมากกว่าสำหรับการดำเนินการอ่าน/เขียนส่วนใหญ่ แต่บางครั้งอาจมีประโยชน์ในการประมาณการเอียงของนาฬิกาของไคลเอ็นต์เมื่อเทียบกับเซิร์ฟเวอร์ของ Firebase Realtime Database คุณสามารถแนบการเรียกกลับไปยังตำแหน่ง /.info/serverTimeOffset
เพื่อรับค่าในหน่วยมิลลิวินาที ที่ไคลเอ็นต์ Firebase Realtime Database เพิ่มลงในเวลาที่รายงานในเครื่อง (เวลาของยุคในหน่วยมิลลิวินาที) เพื่อประเมินเวลาเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของออฟเซ็ตนี้อาจได้รับผลกระทบจากเวลาแฝงของเครือข่าย และมีประโยชน์อย่างมากในการค้นหาความแตกต่างอย่างมาก (> 1 วินาที) ของเวลานาฬิกา
สวิฟต์
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)") } })
วัตถุประสงค์-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); }];
ตัวอย่างการแสดงตน App
ด้วยการรวมการดำเนินการยกเลิกการเชื่อมต่อเข้ากับการตรวจสอบสถานะการเชื่อมต่อและการประทับเวลาของเซิร์ฟเวอร์ คุณสามารถสร้างระบบการแสดงตนของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละคนเก็บข้อมูลไว้ที่ตำแหน่งฐานข้อมูลเพื่อระบุว่าไคลเอ็นต์ Realtime Database ออนไลน์อยู่หรือไม่ ลูกค้าตั้งค่าตำแหน่งนี้เป็นจริงเมื่อออนไลน์และประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้ระบุเวลาล่าสุดที่ผู้ใช้ออนไลน์ออนไลน์
โปรดทราบว่าแอปของคุณควรจัดคิวการดำเนินการตัดการเชื่อมต่อก่อนที่ผู้ใช้จะถูกทำเครื่องหมายว่าออนไลน์ เพื่อหลีกเลี่ยงสภาวะการแย่งชิงในกรณีที่การเชื่อมต่อเครือข่ายของไคลเอ็นต์ขาดหายไปก่อนที่จะสามารถส่งคำสั่งทั้งสองไปยังเซิร์ฟเวอร์ได้
นี่คือระบบการแสดงตนของผู้ใช้อย่างง่าย:
สวิฟต์
// 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()) })
วัตถุประสงค์-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]]; } }];