Если вы используете 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. Это позволит вам постепенно внедрить новое решение.
Сравнение кода
Parse 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
Объекты
В 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
}
}];
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
}
}];
Связи между данными
Объект 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];
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);
}];
Рекомендуемая стратегия переноса
Переосмыслите свои данные
Firebase Realtime Database оптимизирован для синхронизации данных за миллисекунды на всех подключенных клиентах, и результирующая структура данных отличается от основных данных Parse. Это означает, что на первом этапе переноса вам нужно определить, какие изменения потребуются для ваших данных, в том числе:
- Как сопоставить объекты Parse с данными Firebase
- Если у вас есть родительско-дочерние отношения, как разделить данные по разным путям, чтобы их можно было эффективно скачать отдельными вызовами.
Перенос данных
После того как вы решите, как структурировать данные в Firebase, вам нужно спланировать, как обрабатывать период, в течение которого вашему приложению необходимо выполнять запись в обе базы данных. Доступны следующие варианты:
Фоновая синхронизация
В этом случае у вас будет две версии приложения: старая, в которой используется Parse, и новая, в которой используется Firebase. Синхронизация между двумя базами данных выполняется с помощью Parse Cloud Code (из Parse в Firebase), а ваш код отслеживает изменения в Firebase и синхронизирует их с Parse. Прежде чем начать работу с новой версией, необходимо:
- Преобразуйте существующие данные Parse в новую структуру Firebase и запишите их в Firebase Realtime Database.
- Напишите функции Parse Cloud Code, которые используют REST API Firebase для записи в Firebase Realtime Database изменений, внесенных в данные Parse старыми клиентами.
- Напишите и разверните код, который отслеживает изменения в Firebase и синхронизирует их с базой данных Parse.
В этом случае старый и новый код будут разделены, а клиенты останутся простыми. Сложность этого сценария заключается в обработке больших наборов данных при первоначальном экспорте и обеспечении того, чтобы двунаправленная синхронизация не генерировала бесконечную рекурсию.
Двойная запись
В этом сценарии вы пишете новую версию приложения, которая использует и Firebase, и Parse, а также Parse Cloud Code для синхронизации изменений, внесенных старыми клиентами, из Parse Data в Firebase Realtime Database. Когда достаточное количество пользователей перейдет на версию приложения, в которой используется только Parse, вы сможете удалить код Parse из версии с двойной записью.
В этом случае код на стороне сервера не требуется. Недостатки этого подхода заключаются в том, что данные, к которым не осуществляется доступ, не переносятся, а размер приложения увеличивается из-за использования обоих SDK.
Firebase Authentication
Firebase Authentication может выполнять аутентификацию пользователей с помощью паролей и популярных поставщиков федеративной идентификационной информации, таких как Google, Facebook и Twitter. Кроме того, в Firebase есть библиотеки пользовательского интерфейса, которые позволяют сэкономить значительные средства, необходимые для реализации и поддержки полной аутентификации в приложении на всех платформах.
Подробную информацию можно найти в документации по Firebase Authentication.
Различия с авторизацией Parse
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"];
}
}];
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. Кроме того, закодируйте значения атрибута "идентификатор группы товаров" bcryptPassword из Parse в формате Base64 и используйте их в поле "идентификатор группы товаров" 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 Database, используя стратегии, описанные в разделе Перенос данных. Если вы перенесете аккаунты, как описано в разделе Перенос аккаунтов, у аккаунтов Firebase будут те же идентификаторы, что и у аккаунтов Parse. Это позволит вам легко перенести и воспроизвести любые связи, основанные на идентификаторе пользователя.
Firebase Cloud Messaging
Firebase Cloud Messaging (FCM) – это кроссплатформенное решение для обмена сообщениями, которое позволяет надежно доставлять сообщения и уведомления бесплатно. Конструктор уведомлений – это бесплатный сервис, созданный на основе Firebase Cloud Messaging и позволяющий разработчикам мобильных приложений отправлять целевые уведомления пользователям.
Дополнительную информацию можно найти в документации по Firebase Cloud Messaging .
Различия с push-уведомлениями Parse
С каждым приложением Parse, установленным на устройстве, зарегистрированном для получения уведомлений, связан объект Installation, в котором хранятся все данные, необходимые для таргетинга уведомлений.
Installation – это подкласс PFUser, поэтому вы можете добавить в экземпляры Installation любые дополнительные данные.
В Компоновщике уведомлений есть готовые сегменты пользователей, созданные на основе таких данных, как приложение, его версия и язык устройства. Вы можете создавать более сложные сегменты пользователей, используя Google Analytics события и свойства для формирования аудиторий. Подробную информацию вы найдете в руководстве по аудиториям. Эти данные о таргетинге не видны в Firebase Realtime Database.
Рекомендуемая стратегия переноса
Перенос токенов устройств
Parse использует токены устройств APNs для таргетинга на установки при отправке уведомлений, а FCM – токены регистрации FCM, сопоставленные с токенами устройств APNs. Просто добавьте FCMSDK в приложение для 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, поддерживая пользователей как старой, так и новой версий. Когда достаточное количество пользователей перейдет на новую версию приложения, вы сможете прекратить поддержку старой версии и отправлять уведомления только через FCM.
Дополнительную информацию можно найти в документации по темам FCM.
Firebase Remote Config
Firebase Remote Config – это облачный сервис, который позволяет менять поведение и внешний вид приложения без выпуска обновлений приложения, которые пользователям приходится скачивать. При использовании Remote Config достаточно задать значения параметров по умолчанию, определяющие поведение и внешний вид приложения, а потом с помощью консоли Firebase переопределять эти значения для всех пользователей приложения или для отдельных сегментов пользовательской базы.
Firebase Remote Config может быть очень полезен при переносе, если вы хотите протестировать разные решения и иметь возможность динамически переключать больше клиентов на другого поставщика. Например, если у вас есть версия приложения, которая использует для данных и Firebase, и Parse, вы можете использовать правило случайного процентиля, чтобы определить, какие клиенты читают из Firebase, и постепенно увеличивать процент.
Чтобы узнать больше о Firebase Remote Config, ознакомьтесь с вводной статьей о Remote Config.
Различия с Parse Config
С помощью Parse Config вы можете добавлять пары "ключ-значение" в приложение на панели управления 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!";
}
}];
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;