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

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

Постоянство диска

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

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

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

Быстрый

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

Цель-C

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

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

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

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

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

Сохранение свежести данных

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

Быстрый

Примечание. Этот продукт Firebase недоступен для цели App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Цель-C

Примечание. Этот продукт Firebase недоступен для цели App Clip.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

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

Быстрый

Примечание. Этот продукт Firebase недоступен для цели App Clip.
scoresRef.keepSynced(false)

Цель-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")")
}

Цель-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")")
}

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

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

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

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

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

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

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

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

Быстрый

Примечание. Этот продукт Firebase недоступен для цели App Clip.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Цель-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)")
  }
}

Цель-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()

Цель-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")
  }
})

Цель-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 как ложь, это не гарантирует, что другой клиент также прочтет ложь.

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

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

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

Быстрый

Примечание. Этот продукт Firebase недоступен для цели App Clip.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Цель-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)")
  }
})

Цель-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())
})

Цель-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]];
  }
}];