قابلیت های آفلاین در پلتفرم های اپل

برنامه‌های Firebase حتی اگر برنامه شما به طور موقت اتصال شبکه خود را از دست بدهد، کار می‌کنند. علاوه بر این، Firebase ابزارهایی برای ذخیره داده‌ها به صورت محلی، مدیریت حضور و مدیریت تأخیر ارائه می‌دهد.

پایداری دیسک

برنامه‌های Firebase به طور خودکار وقفه‌های موقت شبکه را مدیریت می‌کنند. داده‌های ذخیره شده در حالت آفلاین در دسترس هستند و Firebase پس از برقراری مجدد اتصال شبکه، هرگونه نوشتن را دوباره ارسال می‌کند.

وقتی قابلیت ماندگاری دیسک را فعال می‌کنید، برنامه شما داده‌ها را به صورت محلی روی دستگاه می‌نویسد تا برنامه بتواند وضعیت را در حالت آفلاین حفظ کند، حتی اگر کاربر یا سیستم عامل برنامه را مجدداً راه‌اندازی کند.

شما می‌توانید تنها با یک خط کد، قابلیت ماندگاری دیسک را فعال کنید.

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
Database.database().isPersistenceEnabled = true

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
[FIRDatabase database].persistenceEnabled = YES;

رفتار مداوم

با فعال کردن پایداری، هر داده‌ای که کلاینت Firebase Realtime Database در حالت آنلاین همگام‌سازی می‌کند، روی دیسک باقی می‌ماند و حتی زمانی که کاربر یا سیستم عامل برنامه را مجدداً راه‌اندازی کند، به صورت آفلاین در دسترس است. این بدان معناست که برنامه شما با استفاده از داده‌های محلی ذخیره شده در حافظه پنهان، مانند حالت آنلاین کار می‌کند. فراخوانی‌های شنونده برای به‌روزرسانی‌های محلی همچنان فعال خواهند بود.

کلاینت Firebase Realtime Database به طور خودکار صفی از تمام عملیات نوشتن که در حالت آفلاین بودن برنامه شما انجام می‌شوند را نگه می‌دارد. وقتی قابلیت پایداری فعال باشد، این صف نیز در دیسک ذخیره می‌شود، بنابراین تمام نوشته‌های شما هنگام راه‌اندازی مجدد برنامه توسط کاربر یا سیستم عامل در دسترس هستند. وقتی برنامه دوباره متصل می‌شود، تمام عملیات به سرور Firebase Realtime Database ارسال می‌شوند.

اگر برنامه شما از احراز هویت فایربیس استفاده می‌کند، کلاینت Firebase Realtime Database توکن احراز هویت کاربر را در طول راه‌اندازی مجدد برنامه حفظ می‌کند. اگر توکن احراز هویت در حالی که برنامه شما آفلاین است منقضی شود، کلاینت عملیات نوشتن را تا زمانی که برنامه شما کاربر را دوباره احراز هویت کند، متوقف می‌کند، در غیر این صورت عملیات نوشتن ممکن است به دلیل قوانین امنیتی با شکست مواجه شود.

تازه نگه داشتن داده‌ها

Firebase Realtime Database یک کپی محلی از داده‌ها را برای شنوندگان فعال همگام‌سازی و ذخیره می‌کند. علاوه بر این، می‌توانید مکان‌های خاصی را در همگام‌سازی نگه دارید.

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

کلاینت Firebase Realtime Database به طور خودکار داده‌ها را در این مکان‌ها دانلود می‌کند و حتی اگر مرجع هیچ شنونده‌ی فعالی نداشته باشد، آن‌ها را همگام‌سازی نگه می‌دارد. می‌توانید همگام‌سازی را با خط کد زیر دوباره غیرفعال کنید.

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
scoresRef.keepSynced(false)

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
[scoresRef keepSynced:NO];

به طور پیش‌فرض، 10 مگابایت از داده‌های همگام‌سازی‌شده قبلی در حافظه پنهان (cache) ذخیره می‌شود. این مقدار برای اکثر برنامه‌ها کافی است. اگر حافظه پنهان از اندازه پیکربندی‌شده خود بزرگتر شود، Firebase Realtime Database داده‌هایی را که اخیراً کمتر استفاده شده‌اند، پاک می‌کند. داده‌هایی که همگام‌سازی شده‌اند، از حافظه پنهان پاک نمی‌شوند.

جستجوی آفلاین داده‌ها

Firebase Realtime Database داده‌های بازگشتی از یک پرس‌وجو را برای استفاده در حالت آفلاین ذخیره می‌کند. برای پرس‌وجوهایی که در حالت آفلاین ساخته شده‌اند، Firebase Realtime Database به کار خود برای داده‌های بارگذاری شده‌ی قبلی ادامه می‌دهد. اگر داده‌های درخواستی بارگذاری نشده باشند، Firebase Realtime Database داده‌ها را از حافظه‌ی پنهان محلی بارگذاری می‌کند. هنگامی که اتصال شبکه دوباره در دسترس قرار گیرد، داده‌ها بارگذاری شده و پرس‌وجو را منعکس می‌کنند.

برای مثال، این کد برای چهار مورد آخر در Firebase Realtime Database پرس‌وجو می‌کند.

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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")")
}

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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);
    }];

فرض کنید که کاربر اتصال را از دست می‌دهد، آفلاین می‌شود و برنامه را مجدداً راه‌اندازی می‌کند. در حالی که هنوز آفلاین است، برنامه برای دو مورد آخر از همان مکان پرس‌وجو می‌کند. این پرس‌وجو با موفقیت دو مورد آخر را برمی‌گرداند زیرا برنامه هر چهار مورد را در پرس‌وجوی بالا بارگذاری کرده است.

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
[[[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 آورده شده است:

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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 استفاده کند:

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

یک رویداد onDisconnect همچنین می‌تواند با فراخوانی .cancel() لغو شود:

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

تشخیص وضعیت اتصال

برای بسیاری از ویژگی‌های مرتبط با حضور، مفید است که برنامه شما بداند چه زمانی آنلاین یا آفلاین است. Firebase Realtime Database یک مکان ویژه در /.info/connected فراهم می‌کند که هر بار وضعیت اتصال کلاینت Firebase Realtime Database به‌روزرسانی می‌شود. در اینجا مثالی آورده شده است:

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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")
  }
})

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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 فراهم می‌کند:

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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 ثانیه) در زمان ساعت مفید است.

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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)")
  }
})

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
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 تنظیم می‌کنند و هنگام قطع ارتباط، یک مهر زمانی. این مهر زمانی آخرین باری را که کاربر مشخص آنلاین بوده است، نشان می‌دهد.

توجه داشته باشید که برنامه شما باید عملیات قطع ارتباط را قبل از اینکه کاربر به صورت آنلاین علامت‌گذاری شود، در صف قرار دهد تا از هرگونه شرایط رقابتی در صورت قطع اتصال شبکه کلاینت قبل از ارسال هر دو دستور به سرور، جلوگیری شود.

در اینجا یک سیستم حضور کاربر ساده آمده است:

سویفت

توجه: این محصول Firebase در App Clip target در دسترس نیست.
// 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())
})

هدف-سی

توجه: این محصول Firebase در App Clip target در دسترس نیست.
// 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]];
  }
}];