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

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

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

Гугл Аналитика

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

См. документы Google Analytics , чтобы узнать больше.

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

Использование разных поставщиков аналитики — распространенный сценарий, который легко применим к Google Analytics. Просто добавьте его в свое приложение, чтобы получать преимущества от событий и свойств пользователей, которые Google 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];

Гугл Аналитика

// 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 в реальном времени

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

Дополнительные сведения см. в документации по базе данных Firebase Realtime .

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

Объекты

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Аутентификация Firebase

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

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

Различия с Parse Auth

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

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

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

Разобрать
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"];
  }
}];
Firebase
[[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.

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

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

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

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

Облачные сообщения Firebase

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

См. документы Firebase Cloud Messaging , чтобы узнать больше.

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

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

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

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

Перенос токенов устройств

В то время как 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 (succedeed) {
    [[FIRMessaging messaging] subscribeToTopic:@"/topics/Giants"];
  } else {
    // Something went wrong unsubscribing
  }
}];

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

Дополнительные сведения см. в документации по темам FCM .

Удаленная конфигурация Firebase

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

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

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

Отличия от Parse Config

С помощью Parse config вы можете добавить пары ключ/значение в свое приложение на панели Parse Config Dashboard, а затем получить 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!";
  }
}];

Firebase

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;