ความสามารถออฟไลน์บนแพลตฟอร์ม Apple

แอปพลิเคชัน Firebase จะทำงานได้แม้แอปของคุณจะสูญเสียเครือข่ายชั่วคราว การเชื่อมต่อ นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับ การยืนยันข้อมูลภายในเครื่อง ในการจัดการการตรวจหาบุคคลในบ้าน และการจัดการกับเวลาในการตอบสนอง

ความต่อเนื่องของดิสก์

แอป Firebase จะจัดการการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลแคชจะใช้งานได้ขณะที่ออฟไลน์และ Firebase จะส่งการเขียนซ้ำ เมื่อการเชื่อมต่อเครือข่ายกลับมาทำงานอีกครั้ง

เมื่อคุณเปิดใช้การคงอยู่ของดิสก์ แอปจะเขียนข้อมูลลงในเครื่องไปยัง อุปกรณ์ของคุณเพื่อให้แอปรักษาสถานะขณะออฟไลน์ได้แม้ว่าผู้ใช้จะ หรือระบบปฏิบัติการรีสตาร์ทแอป

คุณเปิดใช้การคงอยู่ของดิสก์ได้ด้วยโค้ดเพียงบรรทัดเดียว

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
Database.database().isPersistenceEnabled = true

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[FIRDatabase database].persistenceEnabled = YES;

พฤติกรรมอย่างต่อเนื่อง

เมื่อเปิดใช้การถาวร ข้อมูลทั้งหมดที่ไคลเอ็นต์ Firebase Realtime Database จะซิงค์ขณะที่ออนไลน์ยังคงอยู่ที่ดิสก์และพร้อมใช้งานแบบออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม ซึ่งหมายความว่า แอปพลิเคชันจะทำงานเช่นเดียวกับที่ออนไลน์โดยใช้ข้อมูลในตัวเครื่องที่จัดเก็บในแคช Callback ของผู้ฟังจะยังคงเริ่มทำงานต่อไปเพื่ออัปเดตในพื้นที่

ไคลเอ็นต์ Firebase Realtime Database จะเก็บคิวของทั้งหมดโดยอัตโนมัติ เขียนการดำเนินการที่เกิดขึ้นขณะที่แอปของคุณออฟไลน์ เมื่อเปิดใช้การถาวร คิวนี้จะคงอยู่ในดิสก์ด้วย ของการเขียนจะพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการ รีสตาร์ทแอป เมื่อแอปกลับมาเชื่อมต่อได้อีกครั้ง ระบบจะส่งการดำเนินการไปยังเซิร์ฟเวอร์ Firebase Realtime Database

หากแอปของคุณใช้ การตรวจสอบสิทธิ์ของ Firebase ไคลเอ็นต์ Firebase Realtime Database จะใช้การตรวจสอบสิทธิ์ของผู้ใช้ต่อไป โทเค็นในการรีสตาร์ทแอป หากโทเค็นการตรวจสอบสิทธิ์หมดอายุขณะที่แอปของคุณออฟไลน์อยู่ ไคลเอ็นต์จะหยุดชั่วคราว เขียนการดำเนินการจนกว่าแอปจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง มิฉะนั้น การดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย

อัปเดตข้อมูลให้ใหม่อยู่เสมอ

Firebase Realtime Database จะซิงค์และจัดเก็บสำเนาของข้อมูลภายในเครื่อง ข้อมูลของผู้ฟังที่ฟังอยู่ นอกจากนี้ คุณสามารถเก็บสถานที่ตั้ง ซิงค์กัน

Swift

หมายเหตุ: ผลิตภัณฑ์ 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 Realtime Database จะดาวน์โหลดข้อมูลโดยอัตโนมัติที่ ตำแหน่งเหล่านี้และทำให้ข้อมูลตรงกัน แม้ว่าข้อมูลอ้างอิงจะไม่มี ผู้ฟังที่ฟังอยู่ คุณสามารถปิดการซิงค์ข้อมูลอีกครั้งได้ด้วย บรรทัดโค้ดต่อไปนี้

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
scoresRef.keepSynced(false)

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[scoresRef keepSynced:NO];

โดยค่าเริ่มต้น ระบบจะแคชข้อมูลที่ซิงค์ไว้ก่อนหน้านี้ 10 MB ควรเป็น เพียงพอสำหรับแอปพลิเคชันส่วนใหญ่ หากแคชมีขนาดใหญ่กว่าขนาดที่กำหนดค่าไว้ Firebase Realtime Database จะล้างข้อมูลที่ใช้ล่าสุดออกถาวร โดยข้อมูลที่ซิงค์ไว้จะไม่ถูกลบออกจากแคชถาวร

การค้นหาข้อมูลแบบออฟไลน์

Firebase Realtime Database จัดเก็บข้อมูลที่แสดงผลจากการค้นหาสำหรับการใช้งาน เมื่อออฟไลน์ สำหรับการค้นหาที่สร้างขึ้นขณะออฟไลน์ Firebase Realtime Database จะยังคงทำงานต่อไปสำหรับข้อมูลที่โหลดก่อนหน้านี้ หากข้อมูลที่ขอไม่โหลด Firebase Realtime Database จะโหลด จากแคชในเครื่อง เมื่อการเชื่อมต่อเครือข่ายใช้งานได้อีกครั้ง จะโหลดข้อมูลและจะแสดงการค้นหา

ตัวอย่างเช่น โค้ดนี้จะสอบถามถึงพารามิเตอร์ 4 รายการใน Firebase Realtime Database ของคะแนน

Swift

หมายเหตุ: ผลิตภัณฑ์ 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);
    }];

สมมติว่าผู้ใช้ขาดการเชื่อมต่อ ออฟไลน์ และรีสตาร์ทแอป ขณะที่ออฟไลน์ แอปจะค้นหาสำหรับ 2 รายการสุดท้ายจาก สถานที่เดียวกัน การค้นหานี้จะแสดง 2 รายการสุดท้ายได้สำเร็จ เพราะแอปโหลดขึ้นมาทั้ง 4 รายการในการค้นหาด้านบน

Swift

หมายเหตุ: ผลิตภัณฑ์ 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 Realtime Database เพิ่ม "เพิ่มเด็ก" เหตุการณ์สำหรับไดโนเสาร์ 2 ตัวที่ทำคะแนนสูงสุดโดยใช้ แคชที่เก็บไว้ แต่จะไม่เพิ่ม "ค่า" เนื่องจากแอป ไม่เคยดำเนินการค้นหานั้นขณะที่ออนไลน์

หากแอปจะขอรายการ 6 รายการล่าสุดขณะออฟไลน์ "เพิ่มเด็ก" เหตุการณ์ของทั้ง 4 รายการที่แคชไว้ได้ทันที เมื่อ อุปกรณ์กลับมาออนไลน์อีกครั้ง ไคลเอ็นต์ Firebase Realtime Database จะซิงค์ กับเซิร์ฟเวอร์ แล้วส่ง "เพิ่มเด็ก" 2 คนสุดท้าย และ "value" กิจกรรมของแอป

การจัดการธุรกรรมแบบออฟไลน์

ธุรกรรมที่เกิดขึ้นขณะที่แอปออฟไลน์จะอยู่ในคิว เมื่อแอปกลับมาเชื่อมต่อเครือข่ายได้ ระบบจะส่งธุรกรรมไปยัง เซิร์ฟเวอร์ Realtime Database

การจัดการการตรวจหาบุคคล

ในแอปพลิเคชันแบบเรียลไทม์ การตรวจหาว่าไคลเอ็นต์เมื่อใด เชื่อมต่อและยกเลิกการเชื่อมต่อ ตัวอย่างเช่น คุณอาจ ต้องการทำเครื่องหมายผู้ใช้ว่า "ออฟไลน์" เมื่อลูกค้ายกเลิกการเชื่อมต่อ

ไคลเอ็นต์ฐานข้อมูลของ Firebase ระบุค่าพื้นฐานง่ายๆ ซึ่งคุณสามารถใช้เพื่อ เขียนลงในฐานข้อมูลเมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อจากฐานข้อมูล Firebase เซิร์ฟเวอร์ การอัปเดตเหล่านี้จะเกิดขึ้นไม่ว่าไคลเอ็นต์จะตัดการเชื่อมต่ออย่างชัดเจนหรือไม่ คุณจึงไว้วางใจให้ล้างข้อมูลได้แม้การเชื่อมต่อจะหลุด หรือไคลเอ็นต์ขัดข้อง การดำเนินการเขียนทั้งหมด รวมถึงการตั้งค่า การอัปเดตและการนำออก สามารถดำเนินการได้เมื่อยกเลิกการเชื่อมต่อ

ต่อไปนี้เป็นตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อยกเลิกการเชื่อมต่อโดยใช้ onDisconnect พื้นฐาน:

Swift

หมายเหตุ: ผลิตภัณฑ์ 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() อยู่ที่เซิร์ฟเวอร์ Firebase Realtime Database เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเพื่อ ตรวจสอบว่าผู้ใช้สามารถทำกิจกรรมการเขียนตามที่ขอได้ แล้วแจ้งข้อมูล แอปของคุณหากไม่ถูกต้อง จากนั้นเซิร์ฟเวอร์ ติดตามดูการเชื่อมต่อ หากการเชื่อมต่อหมดเวลา หรือ มีการปิดอย่างต่อเนื่องโดยไคลเอ็นต์ Realtime Database เซิร์ฟเวอร์จะตรวจสอบความปลอดภัย ครั้งที่ 2 (เพื่อให้มั่นใจว่าการดำเนินการยังคงถูกต้อง) แล้วจึงเรียกใช้ กิจกรรมนั้น

แอปของคุณสามารถใช้ Callback ในการดำเนินการเขียน เพื่อตรวจสอบว่าการแนบ onDisconnect ถูกต้อง

Swift

หมายเหตุ: ผลิตภัณฑ์ 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() ดังนี้

Swift

หมายเหตุ: ผลิตภัณฑ์ 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 Realtime Database ระบุสถานที่พิเศษที่ /.info/connected ซึ่ง จะอัปเดตทุกครั้งที่ไคลเอ็นต์ Firebase Realtime Database สถานะการเชื่อมต่อ การเปลี่ยนแปลง มีตัวอย่างดังต่อไปนี้

Swift

หมายเหตุ: ผลิตภัณฑ์ 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 เป็นค่าบูลีนที่ไม่ใช่ ซิงค์ระหว่างไคลเอ็นต์ Realtime Database รายการเนื่องจากค่านี้คือ ขึ้นอยู่กับสถานะของไคลเอ็นต์ กล่าวคือ หากลูกค้ารายหนึ่ง อ่านว่า /.info/connected เป็นเท็จ นี่ไม่ใช่ ให้รับประกันว่าไคลเอ็นต์แยกต่างหากจะอ่าน false ด้วย

การจัดการเวลาในการตอบสนอง

การประทับเวลาของเซิร์ฟเวอร์

เซิร์ฟเวอร์ Firebase Realtime Database มีกลไกในการแทรก การประทับเวลาที่สร้างขึ้นบนเซิร์ฟเวอร์ในรูปแบบข้อมูล ฟีเจอร์นี้เมื่อใช้ร่วมกับ onDisconnect ให้วิธีง่ายๆ ในการจดบันทึก เวลาที่ไคลเอ็นต์ Realtime Database ยกเลิกการเชื่อมต่อ:

Swift

หมายเหตุ: ผลิตภัณฑ์ 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 Realtime Database คุณ สามารถแนบ Callback ไปยังตำแหน่ง /.info/serverTimeOffset เพื่อรับค่าเป็นมิลลิวินาทีที่ไคลเอ็นต์ Firebase Realtime Database เพิ่มลงในเวลาที่รายงานในท้องถิ่น (เวลา Epoch ในหน่วยมิลลิวินาที) เพื่อประมาณการ เวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของออฟเซ็ตนี้อาจได้รับผลกระทบจาก เวลาในการตอบสนองของเครือข่าย และ มีประโยชน์สำหรับการค้นพบ เวลาของนาฬิกามีความคลาดเคลื่อนมาก (> 1 วินาที)

Swift

หมายเหตุ: ผลิตภัณฑ์ 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);
}];

แอปตัวอย่างการตรวจหาบุคคล

ผสานรวมการดำเนินการยกเลิกการเชื่อมต่อเข้ากับการตรวจสอบสถานะการเชื่อมต่อและ การประทับเวลาของเซิร์ฟเวอร์ คุณสามารถสร้างระบบตรวจหาบุคคลในบ้านของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละคนจัดเก็บข้อมูลที่ตำแหน่งฐานข้อมูลเพื่อระบุว่ามีการ ลูกค้า Realtime Database รายออนไลน์ ลูกค้าตั้งค่าตำแหน่งนี้เป็น "จริง" เมื่อ ผู้ใช้กลับมาออนไลน์และมีการประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้ ระบุเวลาล่าสุดที่ผู้ใช้ที่ระบุออนไลน์

โปรดทราบว่าแอปของคุณควรจัดคิวการดำเนินการยกเลิกการเชื่อมต่อก่อนที่ผู้ใช้จะ ที่ระบุทางออนไลน์ เพื่อหลีกเลี่ยงเงื่อนไขการแข่งขันในกรณีที่ลูกค้า การเชื่อมต่อเครือข่ายขาดหายก่อนที่จะส่งทั้งสองคำสั่งไปยังเซิร์ฟเวอร์

ต่อไปนี้เป็นระบบการตรวจหาผู้ใช้แบบง่าย:

Swift

หมายเหตุ: ผลิตภัณฑ์ 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 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

หมายเหตุ: ผลิตภัณฑ์ 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]];
  }
}];