برنامههای Firebase حتی اگر برنامه شما به طور موقت اتصال شبکه خود را از دست بدهد، کار میکنند. علاوه بر این، Firebase ابزارهایی برای ذخیره دادهها به صورت محلی، مدیریت حضور و مدیریت تأخیر ارائه میدهد.
پایداری دیسک
برنامههای Firebase به طور خودکار وقفههای موقت شبکه را مدیریت میکنند. دادههای ذخیره شده در حالت آفلاین در دسترس هستند و Firebase پس از برقراری مجدد اتصال شبکه، هرگونه نوشتن را دوباره ارسال میکند.
وقتی قابلیت ماندگاری دیسک را فعال میکنید، برنامه شما دادهها را به صورت محلی روی دستگاه مینویسد تا برنامه بتواند وضعیت را در حالت آفلاین حفظ کند، حتی اگر کاربر یا سیستم عامل برنامه را مجدداً راهاندازی کند.
شما میتوانید تنها با یک خط کد، قابلیت ماندگاری دیسک را فعال کنید.
سویفت
Database.database().isPersistenceEnabled = true
هدف-سی
[FIRDatabase database].persistenceEnabled = YES;
رفتار مداوم
با فعال کردن پایداری، هر دادهای که کلاینت Firebase Realtime Database در حالت آنلاین همگامسازی میکند، روی دیسک باقی میماند و حتی زمانی که کاربر یا سیستم عامل برنامه را مجدداً راهاندازی کند، به صورت آفلاین در دسترس است. این بدان معناست که برنامه شما با استفاده از دادههای محلی ذخیره شده در حافظه پنهان، مانند حالت آنلاین کار میکند. فراخوانیهای شنونده برای بهروزرسانیهای محلی همچنان فعال خواهند بود.
کلاینت Firebase Realtime Database به طور خودکار صفی از تمام عملیات نوشتن که در حالت آفلاین بودن برنامه شما انجام میشوند را نگه میدارد. وقتی قابلیت پایداری فعال باشد، این صف نیز در دیسک ذخیره میشود، بنابراین تمام نوشتههای شما هنگام راهاندازی مجدد برنامه توسط کاربر یا سیستم عامل در دسترس هستند. وقتی برنامه دوباره متصل میشود، تمام عملیات به سرور Firebase Realtime Database ارسال میشوند.
اگر برنامه شما از احراز هویت فایربیس استفاده میکند، کلاینت Firebase Realtime Database توکن احراز هویت کاربر را در طول راهاندازی مجدد برنامه حفظ میکند. اگر توکن احراز هویت در حالی که برنامه شما آفلاین است منقضی شود، کلاینت عملیات نوشتن را تا زمانی که برنامه شما کاربر را دوباره احراز هویت کند، متوقف میکند، در غیر این صورت عملیات نوشتن ممکن است به دلیل قوانین امنیتی با شکست مواجه شود.
تازه نگه داشتن دادهها
Firebase Realtime Database یک کپی محلی از دادهها را برای شنوندگان فعال همگامسازی و ذخیره میکند. علاوه بر این، میتوانید مکانهای خاصی را در همگامسازی نگه دارید.
سویفت
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
هدف-سی
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
کلاینت Firebase Realtime Database به طور خودکار دادهها را در این مکانها دانلود میکند و حتی اگر مرجع هیچ شنوندهی فعالی نداشته باشد، آنها را همگامسازی نگه میدارد. میتوانید همگامسازی را با خط کد زیر دوباره غیرفعال کنید.
سویفت
scoresRef.keepSynced(false)
هدف-سی
[scoresRef keepSynced:NO];
به طور پیشفرض، 10 مگابایت از دادههای همگامسازیشده قبلی در حافظه پنهان (cache) ذخیره میشود. این مقدار برای اکثر برنامهها کافی است. اگر حافظه پنهان از اندازه پیکربندیشده خود بزرگتر شود، 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")") }
هدف-سی
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")") }
هدف-سی
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) { NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value); }];
در مثال قبلی، کلاینت Firebase Realtime Database با استفاده از حافظه پنهان دائمی، رویدادهای «child added» را برای دو دایناسور با بالاترین امتیاز ایجاد میکند. اما رویداد «value» را ایجاد نمیکند، زیرا برنامه هرگز آن پرسوجو را در حالت آنلاین اجرا نکرده است.
اگر برنامه در حالت آفلاین شش مورد آخر را درخواست کند، رویدادهای «child added» را برای چهار مورد ذخیره شده فوراً دریافت میکند. وقتی دستگاه دوباره آنلاین میشود، کلاینت Firebase Realtime Database با سرور همگامسازی میشود و دو رویداد آخر «child added» و «value» را برای برنامه دریافت میکند.
مدیریت تراکنشها به صورت آفلاین
هر تراکنشی که در حالت آفلاین بودن برنامه انجام شود، در صف انتظار قرار میگیرد. به محض اینکه برنامه دوباره به شبکه متصل شود، تراکنشها به سرور Realtime Database ارسال میشوند.
مدیریت حضور
در برنامههای بلادرنگ، اغلب تشخیص زمان اتصال و قطع اتصال کلاینتها مفید است. برای مثال، ممکن است بخواهید وقتی کلاینت یک کاربر قطع میشود، او را به عنوان «آفلاین» علامتگذاری کنید.
کلاینتهای پایگاه داده فایربیس، مقادیر اولیه سادهای را ارائه میدهند که میتوانید هنگام قطع ارتباط یک کلاینت از سرورهای پایگاه داده فایربیس، برای نوشتن در پایگاه داده از آنها استفاده کنید. این بهروزرسانیها چه کلاینت به طور کامل قطع شود و چه نشود، رخ میدهند، بنابراین میتوانید برای پاکسازی دادهها حتی در صورت قطع اتصال یا خرابی کلاینت، به آنها اعتماد کنید. تمام عملیات نوشتن، از جمله تنظیم، بهروزرسانی و حذف، میتوانند پس از قطع ارتباط انجام شوند.
در اینجا یک مثال ساده از نوشتن دادهها پس از قطع اتصال با استفاده از تابع اولیه onDisconnect آورده شده است:
سویفت
let presenceRef = Database.database().reference(withPath: "disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnectSetValue("I disconnected!")
هدف-سی
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)")
}
}هدف-سی
[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()هدف-سی
[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") } })
هدف-سی
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())
هدف-سی
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"]; [userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
انحراف ساعت
اگرچه firebase.database.ServerValue.TIMESTAMP بسیار دقیقتر است و برای اکثر عملیات خواندن/نوشتن ترجیح داده میشود، اما گاهی اوقات میتواند برای تخمین انحراف ساعت کلاینت نسبت به سرورهای Firebase Realtime Database مفید باشد. میتوانید یک فراخوانی به مکان /.info/serverTimeOffset پیوست کنید تا مقداری را که کلاینتهای Firebase Realtime Database به زمان گزارش شده محلی (زمان epoch به میلی ثانیه) اضافه میکنند تا زمان سرور را تخمین بزنند، بر حسب میلی ثانیه، به دست آورید. توجه داشته باشید که دقت این انحراف میتواند تحت تأثیر تأخیر شبکه قرار گیرد و بنابراین در درجه اول برای کشف اختلافات بزرگ (> 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)") } })
هدف-سی
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()) })
هدف-سی
// 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]]; } }];