Автономные возможности на iOS

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

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

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

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

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

Быстрый

Database.database().isPersistenceEnabled = true

Цель-C

[FIRDatabase database].persistenceEnabled = YES;

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

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

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

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

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

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

Быстрый

let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Цель-C

FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

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

Быстрый

scoresRef.keepSynced(false)

Цель-C

[scoresRef keepSynced:NO];

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

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

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

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

Быстрый

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

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

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

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

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

Клиенты базы данных 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 в реальном времени. Сервер проверяет безопасность, чтобы убедиться, что пользователь может выполнить запрошенное событие записи, и сообщает вашему приложению, если оно недействительно. Затем сервер контролирует соединение. Если в какой-то момент время ожидания соединения истекает или оно активно закрывается клиентом базы данных реального времени, сервер проверяет безопасность во второй раз (чтобы убедиться, что операция все еще действительна), а затем вызывает событие.

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

Быстрый

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

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

Отметки времени сервера

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

Быстрый

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

Пример приложения Presence

Комбинируя операции отключения с мониторингом состояния подключения и отметками времени сервера, вы можете создать систему присутствия пользователей. В этой системе каждый пользователь хранит данные в расположении базы данных, чтобы указать, подключен ли клиент базы данных реального времени. Клиенты устанавливают для этого местоположения значение 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 let connected = snapshot.value as? Bool, connected 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]];
  }
}];