برنامه های Firebase حتی اگر برنامه شما به طور موقت اتصال شبکه خود را قطع کند، کار می کنند. علاوه بر این، Firebase ابزارهایی را برای تداوم داده ها به صورت محلی، مدیریت حضور و مدیریت تأخیر فراهم می کند.
پایداری دیسک
برنامه های Firebase به طور خودکار وقفه های موقت شبکه را کنترل می کنند. دادههای ذخیرهشده در حافظه پنهان در حالت آفلاین در دسترس هستند و Firebase هنگام بازیابی اتصال شبکه، هر نوشتهای را دوباره ارسال میکند.
وقتی پایداری دیسک را فعال میکنید، برنامه شما دادهها را به صورت محلی در دستگاه مینویسد تا برنامه شما بتواند در حالت آفلاین وضعیت را حفظ کند، حتی اگر کاربر یا سیستم عامل برنامه را مجدداً راهاندازی کند.
شما می توانید پایداری دیسک را تنها با یک خط کد فعال کنید.
سویفت
Database.database().isPersistenceEnabled = true
هدف-C
[FIRDatabase database].persistenceEnabled = YES;
رفتار پایدار
با فعال کردن تداوم، هر دادهای که مشتری Firebase Realtime Database در حالت آنلاین همگامسازی میکند، روی دیسک باقی میماند و به صورت آفلاین در دسترس است، حتی زمانی که کاربر یا سیستم عامل برنامه را مجدداً راهاندازی میکند. این بدان معنی است که برنامه شما با استفاده از داده های محلی ذخیره شده در حافظه پنهان، همانطور که بصورت آنلاین انجام می شود کار می کند. تماسهای شنونده برای بهروزرسانیهای محلی فعال میشوند.
سرویس گیرنده Firebase Realtime Database به طور خودکار یک صف از تمام عملیات نوشتن که در زمانی که برنامه شما آفلاین است انجام می شود نگه می دارد. هنگامی که تداوم فعال است، این صف نیز روی دیسک باقی می ماند تا زمانی که کاربر یا سیستم عامل برنامه را مجددا راه اندازی می کند، همه نوشته های شما در دسترس باشد. هنگامی که برنامه اتصال مجدد را به دست آورد، همه عملیات به سرور Firebase Realtime Database ارسال می شود.
اگر برنامه شما از احراز هویت Firebase استفاده میکند، سرویس گیرنده 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 Realtime Database به طور خودکار داده ها را در این مکان ها دانلود می کند و حتی اگر مرجع هیچ شنونده فعالی نداشته باشد، آنها را همگام نگه می دارد. می توانید همگام سازی را با خط کد زیر خاموش کنید.
سویفت
scoresRef.keepSynced(false)
هدف-C
[scoresRef keepSynced:NO];
به طور پیشفرض، 10 مگابایت از دادههای همگامسازی شده قبلی در حافظه پنهان ذخیره میشود. این باید برای اکثر برنامه ها کافی باشد. اگر حافظه پنهان از اندازه پیکربندی شده خود بیشتر شود، Firebase Realtime Database داده هایی را که اخیراً کمتر استفاده شده اند را پاک می کند. داده هایی که همگام نگه داشته می شوند از حافظه پنهان پاک نمی شوند.
پرس و جو داده ها به صورت آفلاین
Firebase Realtime Database داده های بازگشتی از یک درخواست را برای استفاده در حالت آفلاین ذخیره می کند. برای پرس و جوهایی که در حالت آفلاین ساخته می شوند، Firebase Realtime Database برای داده های بارگیری شده قبلی به کار خود ادامه می دهد. اگر دادههای درخواستی بارگیری نشده باشند، Firebase Realtime Database دادهها را از حافظه پنهان محلی بارگیری میکند. هنگامی که اتصال شبکه دوباره در دسترس است، داده ها بارگیری می شوند و پرس و جو را منعکس می کنند.
به عنوان مثال، این کد برای چهار مورد آخر در Firebase Realtime Database جستجو می کند
سویفت
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 با سرور همگام می شود و دو رویداد نهایی «کودک اضافه شده» و «ارزش» را برای برنامه دریافت می کند.
انجام معاملات آفلاین
هر تراکنشی که در زمانی که برنامه آفلاین است انجام شود، در صف قرار می گیرد. هنگامی که برنامه دوباره اتصال شبکه را به دست آورد، تراکنش ها به سرور 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 کار می کند
هنگامی که یک عملیات onDisconnect()
ایجاد می کنید، این عملیات در سرور Firebase Realtime Database زندگی می کند. سرور امنیت را بررسی می کند تا مطمئن شود کاربر می تواند رویداد نوشتن درخواستی را انجام دهد و در صورت نامعتبر بودن برنامه شما را به اطلاع می رساند. سپس سرور اتصال را کنترل می کند. اگر در هر نقطه ای زمان اتصال تمام شود، یا به طور فعال توسط سرویس گیرنده 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
را به عنوان false بخواند، این تضمینی نیست که یک کلاینت جداگانه نیز false را بخواند.
رسیدگی به تاخیر
مهر زمانی سرور
سرورهای 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); }];
نمونه برنامه حضور
با ترکیب عملیات قطع اتصال با نظارت بر وضعیت اتصال و مهرهای زمانی سرور، می توانید یک سیستم حضور کاربر بسازید. در این سیستم، هر کاربر دادهها را در یک مکان پایگاه داده ذخیره میکند تا مشخص کند آیا یک سرویس گیرنده Realtime Database آنلاین است یا نه. مشتریان زمانی که آنلاین میشوند، این مکان را روی true و هنگام قطع ارتباط یک مهر زمانی تنظیم میکنند. این مهر زمان آخرین باری را که کاربر مورد نظر آنلاین بوده است نشان می دهد.
توجه داشته باشید که برنامه شما باید قبل از اینکه کاربر به صورت آنلاین علامت گذاری شود، عملیات قطع اتصال را در صف قرار دهد تا در صورت قطع شدن اتصال شبکه مشتری قبل از ارسال هر دو دستور به سرور، از هرگونه شرایط مسابقه جلوگیری شود.
در اینجا یک سیستم حضور کاربر ساده وجود دارد:
سویفت
// 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()) })
هدف-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]]; } }];