Перенесите приложение Parse iOS в Firebase

Если вы являетесь пользователем Parse и ищете альтернативное решение Backend as a Service, Firebase может стать идеальным выбором для вашего приложения для iOS.

В этом руководстве описывается, как интегрировать определенные сервисы в ваше приложение. Основные инструкции по настройке Firebase см. в руководстве по настройке iOS+ .

Google Analytics

Google Analytics — это бесплатное решение для измерения приложений, которое дает представление об использовании приложений и взаимодействии с пользователями. Analytics интегрируется со всеми функциями Firebase и предоставляет вам неограниченные отчеты по 500 различным событиям, которые вы можете определить с помощью Firebase SDK.

Дополнительную информацию см. в документации Google Analytics .

Предлагаемая стратегия миграции

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

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

Сравнение кода

Анализ аналитики

// Start collecting data
[PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];

NSDictionary *dimensions = @{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
};
// Send the dimensions to Parse along with the 'search' event
[PFAnalytics trackEvent:@"search" dimensions:dimensions];

Google Analytics

// Obtain the AppMeasurement instance and start collecting data
[FIRApp configure];

// Send the event with your params
[FIRAnalytics logEventWithName:@"search" parameters:@{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
}];

Firebase Realtime Database

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

Дополнительную информацию см. в документации Firebase Realtime Database .

Различия с анализом данных

Объекты

В Parse вы храните PFObject или его подкласс, который содержит пары ключ-значение JSON-совместимых данных. Данные не имеют схемы, что означает, что вам не нужно указывать, какие ключи существуют в каждом PFObject .

Все данные Firebase Realtime Database хранятся в виде объектов JSON, и для PFObject не существует эквивалента; вы просто записываете в дерево JSON значения типов, которые соответствуют доступным типам JSON.

Ниже приведен пример того, как можно сохранить рекорды в игре.

Разобрать
PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"];
gameScore[@"score"] = @1337;
gameScore[@"playerName"] = @"Sean Plott";
gameScore[@"cheatMode"] = @NO;
[gameScore saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succeeded) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Огневая база
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
NSString *key = [[ref child:@"scores"] childByAutoId].key;
NSDictionary *score = @{@"score": @1337,
                        @"playerName": @"Sean Plott",
                        @"cheatMode": @NO};
[key setValue:score withCompletionBlock:^(NSError *error,  FIRDatabaseReference *ref) {
  if (error) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Для получения более подробной информации ознакомьтесь с руководством по чтению и записи данных на платформах Apple .

Отношения между данными

PFObject может иметь связь с другим PFObject : любой объект может использовать другие объекты в качестве значений.

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

Ниже приведен пример того, как вы можете структурировать отношения между сообщениями в приложении для ведения блога и их авторами.

Разобрать
// Create the author
PFObject *myAuthor = [PFObject objectWithClassName:@"Author"];
myAuthor[@"name"] = @"Grace Hopper";
myAuthor[@"birthDate"] = @"December 9, 1906";
myAuthor[@"nickname"] = @"Amazing Grace";

// Create the post
PFObject *myPost = [PFObject objectWithClassName:@"Post"];
myPost[@"title"] = @"Announcing COBOL, a New Programming Language";

// Add a relation between the Post and the Author
myPost[@"parent"] = myAuthor;

// This will save both myAuthor and myPost
[myPost saveInBackground];
Огневая база
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// Create the author
NSString *myAuthorKey = @"ghopper";
NSDictionary *author = @{@"name": @"Grace Hopper",
                         @"birthDate": @"December 9, 1906",
                         @"nickname": @"Amazing Grace"};
// Save the author
[[ref child:myAuthorKey] setValue:author]

// Create and save the post
NSString *key = [[ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"author": myAuthorKey,
                       @"title": @"Announcing COBOL, a New Programming Language"};
[key setValue:post]

Результатом является следующая структура данных.

{
  // Info about the authors
  "authors": {
    "ghopper": {
      "name": "Grace Hopper",
      "date_of_birth": "December 9, 1906",
      "nickname": "Amazing Grace"
    },
    ...
  },
  // Info about the posts: the "author" fields contains the key for the author
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "ghopper",
      "title": "Announcing COBOL, a New Programming Language"
    }
    ...
  }
}
Для получения более подробной информации ознакомьтесь с руководством по структурированию базы данных .

Чтение данных

В Parse вы читаете данные, используя либо идентификатор конкретного объекта Parse, либо выполняя запросы с помощью PFQuery .

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

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

Разобрать
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query whereKey:@"playerName" equalTo:@"Dan Stemkoski"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
  if (!error) {
    for (PFObject *score in objects) {
      NSString *gameScore = score[@"score"];
      NSLog(@"Retrieved: %@", gameScore);
    }
  } else {
    // Log details of the failure
    NSLog(@"Error: %@ %@", error, [error userInfo]);
  }
}];
Огневая база
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
[[[[ref child:@"scores"] queryOrderedByChild:@"playerName"] queryEqualToValue:@"Dan Stemkoski"]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
  // This will fire for each matching child node.
  NSDictionary *score = snapshot.value;
  NSString gameScore = score[@"score"];
  NSLog(@"Retrieved: %@", gameScore);
}];
Дополнительные сведения о доступных типах прослушивателей событий, а также о том, как упорядочивать и фильтровать данные, см. в руководстве по чтению и записи данных на платформах Apple .

Предлагаемая стратегия миграции

Переосмыслите свои данные

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

  • Как ваши объекты Parse должны сопоставляться с данными Firebase
  • Если у вас есть отношения «родитель-потомок», как разделить ваши данные по разным путям, чтобы их можно было эффективно загружать отдельными вызовами.

Перенесите свои данные

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

Фоновая синхронизация

В этом сценарии у вас есть две версии приложения: старая версия, использующая Parse, и новая версия, использующая Firebase. Синхронизация между двумя базами данных осуществляется с помощью Parse Cloud Code (Parse to Firebase), при этом ваш код прослушивает изменения в Firebase и синхронизирует эти изменения с Parse. Прежде чем вы сможете начать использовать новую версию, вам необходимо:

  • Преобразуйте существующие данные анализа в новую структуру Firebase и запишите их в Firebase Realtime Database .
  • Напишите функции синтаксического облачного кода, которые используют REST API Firebase для записи в Firebase Realtime Database изменений, внесенных в данные синтаксического анализа старыми клиентами.
  • Напишите и разверните код, который прослушивает изменения в Firebase и синхронизирует их с базой данных Parse.

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

Двойная запись

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

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

Firebase Authentication

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

Дополнительную информацию см. в документации Firebase Authentication .

Различия с аутентификацией синтаксического анализа

Parse предоставляет специализированный пользовательский класс под названием PFUser , который автоматически выполняет функции, необходимые для управления учетными записями пользователей. PFUser — это подкласс PFObject , что означает, что пользовательские данные доступны в Parse Data и могут быть расширены дополнительными полями, как и любой другой PFObject .

FIRUser имеет фиксированный набор основных свойств — уникальный идентификатор, основной адрес электронной почты, имя и URL-адрес фотографии — которые хранятся в базе данных пользователей отдельного проекта; эти свойства могут быть обновлены пользователем. Вы не можете напрямую добавлять другие свойства к объекту FIRUser ; вместо этого вы можете сохранить дополнительные свойства в Firebase Realtime Database .

Ниже приведен пример того, как можно зарегистрировать пользователя и добавить дополнительное поле номера телефона.

Разобрать
PFUser *user = [PFUser user];
user.username = @"my name";
user.password = @"my pass";
user.email = @"email@example.com";

// other fields can be set just like with PFObject
user[@"phone"] = @"415-392-0202";

[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (!error) {
    // Hooray! Let them use the app now.
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];
Огневая база
[[FIRAuth auth] createUserWithEmail:@"email@example.com"
                           password:@"my pass"
                         completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  if (!error) {
    FIRDatabaseReference *ref = [[FIRDatabase database] reference];
    [[[[ref child:@"users"] child:user.uid] child:@"phone"] setValue:@"415-392-0202"
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];

Предлагаемая стратегия миграции

Перенос учетных записей

Чтобы перенести учетные записи пользователей из Parse в Firebase, экспортируйте базу данных пользователей в файл JSON или CSV, а затем импортируйте файл в свой проект Firebase с помощью команды auth:import Firebase CLI.

Сначала экспортируйте свою пользовательскую базу данных из консоли Parse или из собственной базы данных. Например, файл JSON, экспортированный из консоли Parse, может выглядеть следующим образом:

{ // Username/password user
  "bcryptPassword": "$2a$10$OBp2hxB7TaYZgKyTiY48luawlTuYAU6BqzxJfpHoJMdZmjaF4HFh6",
  "email": "user@example.com",
  "username": "testuser",
  "objectId": "abcde1234",
  ...
},
{ // Facebook user
  "authData": {
    "facebook": {
      "access_token": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
      "expiration_date": "2017-01-02T03:04:05.006Z",
      "id": "1000000000"
    }
  },
  "username": "wXyZ987654321StUv",
  "objectId": "fghij5678",
  ...
}

Затем преобразуйте экспортированный файл в формат, требуемый интерфейсом командной строки Firebase. Используйте objectId ваших пользователей Parse в качестве localId ваших пользователей Firebase. Кроме того, base64 кодирует значения bcryptPassword из Parse и использует их в поле passwordHash . Например:

{
  "users": [
    {
      "localId": "abcde1234",  // Parse objectId
      "email": "user@example.com",
      "displayName": "testuser",
      "passwordHash": "JDJhJDEwJE9CcDJoeEI3VGFZWmdLeVRpWTQ4bHVhd2xUdVlBVTZCcXp4SmZwSG9KTWRabWphRjRIRmg2",
    },
    {
      "localId": "fghij5678",  // Parse objectId
      "displayName": "wXyZ987654321StUv",
      "providerUserInfo": [
        {
          "providerId": "facebook.com",
          "rawId": "1000000000",  // Facebook ID
        }
      ]
    }
  ]
}

Наконец, импортируйте преобразованный файл с помощью Firebase CLI, указав bcrypt в качестве алгоритма хеширования:

firebase auth:import account_file.json --hash-algo=BCRYPT

Перенос пользовательских данных

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

Firebase Cloud Messaging

Firebase Cloud Messaging ( FCM ) — это кроссплатформенное решение для обмена сообщениями, которое позволяет надежно доставлять сообщения и уведомления бесплатно. Компоновщик уведомлений – это бесплатная служба, созданная на базе Firebase Cloud Messaging , которая позволяет отправлять целевые пользовательские уведомления разработчикам мобильных приложений.

Дополнительную информацию см . в документации Firebase Cloud Messaging .

Различия с анализом push-уведомлений

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

Компоновщик уведомлений предоставляет предварительно определенные сегменты пользователей на основе такой информации, как приложение, версия приложения и язык устройства. Вы можете создавать более сложные сегменты пользователей, используя события и свойства Google Analytics для создания аудитории. Чтобы узнать больше, ознакомьтесь со справочным руководством по аудиториям . Эта информация о таргетинге не отображается в Firebase Realtime Database .

Предлагаемая стратегия миграции

Миграция токенов устройств

В то время как Parse использует токены устройств APN для целевых установок для уведомлений, FCM использует токены регистрации FCM , сопоставленные с токенами устройств APN. Просто добавьте FCM SDK в свое приложение Apple, и оно автоматически получит токен FCM .

Перенос каналов в темы FCM

Если вы используете каналы Parse для отправки уведомлений, вы можете перейти на темы FCM , которые предоставляют ту же модель издатель-подписчик. Чтобы обработать переход от Parse к FCM , вы можете написать новую версию приложения, которая использует Parse SDK для отказа от подписки на каналы Parse и FCM SDK для подписки на соответствующие темы FCM .

Например, если ваш пользователь подписан на тему «Гиганты», вы должны сделать что-то вроде:

PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succeeded) {
    [[FIRMessaging messaging] subscribeToTopic:@"/topics/Giants"];
  } else {
    // Something went wrong unsubscribing
  }
}];

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

Дополнительную информацию см . в документации по темам FCM .

Firebase Remote Config

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

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

Чтобы узнать больше о Firebase Remote Config , см. введение Remote Config .

Различия с конфигурацией синтаксического анализа

С помощью конфигурации Parse вы можете добавить пары ключ/значение в свое приложение на панели управления Parse Config, а затем получить PFConfig на клиенте. Каждый экземпляр PFConfig , который вы получаете, всегда является неизменяемым. Когда вы в будущем получите новый PFConfig из сети, он не изменит ни один существующий экземпляр PFConfig , а вместо этого создаст новый и сделает его доступным через currentConfig .

С помощью Firebase Remote Config вы создаете в приложении значения по умолчанию для пар ключ/значение, которые можно переопределить из консоли Firebase, а также можете использовать правила и условия, чтобы предоставлять варианты взаимодействия с пользователем вашего приложения для разных сегментов вашей пользовательской базы. Firebase Remote Config реализует одноэлементный класс, который делает пары ключ/значение доступными для вашего приложения. Первоначально синглтон возвращает значения по умолчанию, которые вы определяете в приложении. Вы можете получить новый набор значений с сервера в любой удобный для вашего приложения момент; после того, как новый набор будет успешно получен, вы можете выбрать, когда его активировать, чтобы новые значения были доступны приложению.

Предлагаемая стратегия миграции

Вы можете перейти к Firebase Remote Config , скопировав пары ключ/значение вашей конфигурации Parse в консоль Firebase, а затем развернув новую версию приложения, использующую Firebase Remote Config .

Если вы хотите поэкспериментировать как с Parse Config, так и Firebase Remote Config , вы можете развернуть новую версию приложения, использующую оба SDK, до тех пор, пока достаточное количество пользователей не перейдет с версии только с Parse.

Сравнение кода

Разобрать

[PFConfig getConfigInBackgroundWithBlock:^(PFConfig *config, NSError *error) {
  if (!error) {
    NSLog(@"Yay! Config was fetched from the server.");
  } else {
    NSLog(@"Failed to fetch. Using Cached Config.");
    config = [PFConfig currentConfig];
  }

  NSString *welcomeMessage = config[@"welcomeMessage"];
  if (!welcomeMessage) {
    NSLog(@"Falling back to default message.");
    welcomeMessage = @"Welcome!";
  }
}];

Огневая база

FIRRemoteConfig remoteConfig = [FIRRemoteConfig remoteConfig];
// Set defaults from a plist file
[remoteConfig setDefaultsFromPlistFileName:@"RemoteConfigDefaults"];

[remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) {
  if (status == FIRRemoteConfigFetchStatusSuccess) {
    NSLog(@"Yay! Config was fetched from the server.");
    // Once the config is successfully fetched it must be activated before newly fetched
    // values are returned.
    [self.remoteConfig activateFetched];
  } else {
    NSLog(@"Failed to fetch. Using last fetched or default.");
  }
}];

// ...

// When this is called, the value of the latest fetched and activated config is returned;
// if there's none, the default value is returned.
NSString welcomeMessage = remoteConfig[@"welcomeMessage"].stringValue;