Автономные возможности на платформах Apple

Приложения Firebase работают, даже если ваше приложение временно теряет сетевое соединение. Кроме того, Firebase предоставляет инструменты для локального сохранения данных, управления присутствием и обработки задержек.

Сохранение данных на диске

Приложения Firebase автоматически обрабатывают временные сбои в работе сети. Кэшированные данные доступны в автономном режиме, а Firebase повторно отправляет любые записи при восстановлении сетевого подключения.

При включении сохранения на диске ваше приложение записывает данные локально на устройство, чтобы приложение могло сохранять свое состояние в автономном режиме, даже если пользователь или операционная система перезапускают приложение.

Вы можете включить сохранение данных на диске с помощью всего одной строки кода.

Быстрый

Примечание: этот продукт Firebase недоступен для целевой платформы App Clip.
Database.database().isPersistenceEnabled = true

Objective-C

Примечание: этот продукт Firebase недоступен для целевой платформы App Clip.
[FIRDatabase database].persistenceEnabled = YES;

Поведение настойчивости

При включении сохранения любые данные, которые клиент Firebase Realtime Database будет синхронизировать в режиме онлайн, сохраняются на диске и доступны в автономном режиме, даже если пользователь или операционная система перезапускают приложение. Это означает, что ваше приложение работает так же, как и в режиме онлайн, используя локальные данные, хранящиеся в кэше. Обратные вызовы прослушивателя будут продолжать срабатывать для локальных обновлений.

Клиент Firebase Realtime Database автоматически сохраняет очередь всех операций записи, которые выполняются, пока ваше приложение находится в автономном режиме. Когда сохранение включено, эта очередь также сохраняется на диске, поэтому все ваши записи доступны, когда пользователь или операционная система перезапускают приложение. Когда приложение восстанавливает подключение, все операции отправляются на сервер Firebase Realtime Database .

Если ваше приложение использует Firebase Authentication , клиент Firebase Realtime Database сохраняет токен аутентификации пользователя при перезапусках приложения. Если токен аутентификации истекает, когда ваше приложение находится в автономном режиме, клиент приостанавливает операции записи до тех пор, пока ваше приложение повторно не аутентифицирует пользователя, в противном случае операции записи могут завершиться ошибкой из-за правил безопасности.

Поддержание актуальности данных

Firebase Realtime Database синхронизирует и хранит локальную копию данных для активных слушателей. Кроме того, вы можете синхронизировать определенные местоположения.

Быстрый

Примечание: этот продукт 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 автоматически загружает данные в эти расположения и синхронизирует их, даже если у ссылки нет активных слушателей. Вы можете отключить синхронизацию с помощью следующей строки кода.

Быстрый

Примечание: этот продукт Firebase недоступен для целевой платформы App Clip.
scoresRef.keepSynced(false)

Objective-C

Примечание: этот продукт Firebase недоступен для целевой платформы App Clip.
[scoresRef keepSynced:NO];

По умолчанию кэшируются 10 МБ ранее синхронизированных данных. Этого должно быть достаточно для большинства приложений. Если кэш превышает заданный размер, Firebase Realtime Database очищает данные, которые использовались реже всего. Данные, которые поддерживаются в синхронизации, не очищаются из кэша.

Запрос данных в автономном режиме

Firebase Realtime Database хранит данные, возвращаемые запросом, для использования в автономном режиме. Для запросов, созданных в автономном режиме, Firebase Realtime Database продолжает работать с ранее загруженными данными. Если запрошенные данные не были загружены, Firebase Realtime Database загружает данные из локального кэша. Когда сетевое подключение снова станет доступно, данные загрузятся и будут отражать запрос.

Например, этот код запрашивает последние четыре элемента в Firebase Realtime Database с оценками.

Быстрый

Примечание: этот продукт 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);
    }];

Предположим, что пользователь теряет соединение, переходит в автономный режим и перезапускает приложение. Пока приложение находится в автономном режиме, оно запрашивает последние два элемента из того же местоположения. Этот запрос успешно вернет последние два элемента, поскольку приложение загрузило все четыре элемента в запросе выше.

Быстрый

Примечание: этот продукт 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 вызывает события «child added» для двух динозавров с наивысшим рейтингом, используя сохраняемый кэш. Но он не вызовет событие «value», поскольку приложение никогда не выполняло этот запрос в режиме онлайн.

Если бы приложение запросило последние шесть элементов в автономном режиме, оно бы сразу получило события «child added» для четырех кэшированных элементов. Когда устройство снова подключается к сети, клиент Firebase Realtime Database синхронизируется с сервером и получает последние два события «child added» и «value» для приложения.

Обработка транзакций в автономном режиме

Любые транзакции, которые выполняются, пока приложение находится в автономном режиме, ставятся в очередь. Как только приложение восстанавливает сетевое подключение, транзакции отправляются на сервер Realtime Database .

Управление присутствием

В приложениях реального времени часто бывает полезно определять, когда клиенты подключаются и отключаются. Например, вы можете захотеть пометить пользователя как «офлайн», когда его клиент отключается.

Клиенты Firebase Database предоставляют простые примитивы, которые можно использовать для записи в базу данных, когда клиент отключается от серверов Firebase Database. Эти обновления происходят независимо от того, отключается ли клиент чисто или нет, поэтому вы можете положиться на них для очистки данных, даже если соединение разорвано или клиент завис. Все операции записи, включая настройку, обновление и удаление, можно выполнять при отключении.

Вот простой пример записи данных при отключении с использованием примитива onDisconnect :

Быстрый

Примечание: этот продукт 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

Когда вы устанавливаете операцию onDisconnect() , операция находится на сервере Firebase Realtime Database . Сервер проверяет безопасность, чтобы убедиться, что пользователь может выполнить запрошенное событие записи, и сообщает вашему приложению, если оно недействительно. Затем сервер отслеживает соединение. Если в какой-либо момент время ожидания соединения истекло или оно активно закрыто клиентом Realtime Database , сервер проверяет безопасность во второй раз (чтобы убедиться, что операция все еще действительна), а затем вызывает событие.

Ваше приложение может использовать обратный вызов при операции записи, чтобы убедиться, что onDisconnect был правильно присоединен:

Быстрый

Примечание: этот продукт 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() :

Быстрый

Примечание: этот продукт 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 изменяется. Вот пример:

Быстрый

Примечание: этот продукт 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, это не гарантирует, что другой клиент также считывает false.

Обработка задержек

Временные метки сервера

Серверы Firebase Realtime Database предоставляют механизм для вставки временных меток, сгенерированных на сервере, в качестве данных. Эта функция в сочетании с onDisconnect обеспечивает простой способ надежного запоминания времени отключения клиента Realtime Database :

Быстрый

Примечание: этот продукт 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 . Вы можете прикрепить обратный вызов к расположению /.info/serverTimeOffset , чтобы получить значение в миллисекундах, которое клиенты Firebase Realtime Database добавляют к локальному сообщенному времени (время эпохи в миллисекундах) для оценки времени сервера. Обратите внимание, что на точность этого смещения может влиять сетевая задержка, поэтому оно полезно в первую очередь для обнаружения больших (> 1 секунды) расхождений во времени часов.

Быстрый

Примечание: этот продукт 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 в сети. Клиенты устанавливают это расположение в значение true, когда они подключаются к сети, и временную метку, когда они отключаются. Эта временная метка указывает последнее время, когда данный пользователь был в сети.

Обратите внимание, что ваше приложение должно поставить операции отключения в очередь до того, как пользователь будет отмечен как находящийся в сети, чтобы избежать состояний гонки в случае, если сетевое соединение клиента будет потеряно до того, как обе команды будут отправлены на сервер.

Вот простая система присутствия пользователя:

Быстрый

Примечание: этот продукт 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]];
  }
}];