Eseguire la migrazione dell'app Parse per iOS a Firebase

Se sei un utente di Parse alla ricerca di una soluzione alternativa di backend come servizio, Firebase potrebbe essere la scelta ideale per la tua app per iOS.

Questa guida descrive come integrare servizi specifici nella tua app. Per le istruzioni di configurazione di base di Firebase, consulta la guida Configurazione di iOS e versioni successive.

Google Analytics

Google Analytics è una soluzione di misurazione delle app senza costi che fornisce approfondimenti sull'utilizzo delle app e sul coinvolgimento degli utenti. Analytics si integra con le funzionalità di Firebase e fornisce report illimitati per un massimo di 500 eventi distinti che puoi definire utilizzando l'SDK Firebase.

Per scoprire di più, consulta la documentazione di Google Analytics.

Strategia di migrazione suggerita

L'utilizzo di diversi fornitori di soluzioni di analisi è uno scenario comune che si applica facilmente a Google Analytics. Aggiungilo alla tua app per usufruire degli eventi e delle proprietà utente raccolti automaticamente daAnalytics, ad esempio prima apertura, aggiornamento dell'app, modello di dispositivo, età.

Per gli eventi e le proprietà utente personalizzati, puoi utilizzare una strategia di scrittura doppia utilizzando sia Parse Analytics sia Google Analytics per registrare eventi e proprietà, il che ti consente di implementare gradualmente la nuova soluzione.

Confronto del codice

Analizza dati

// 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 è un database NoSQL ospitato nel cloud. I dati vengono archiviati come JSON e sincronizzati in tempo reale con ogni client connesso.

Per scoprire di più, consulta la documentazione di Firebase Realtime Database.

Differenze con i dati di analisi

Oggetti

In Parse memorizzi un PFObject o una sottoclasse che contiene coppie chiave-valore di dati compatibili con JSON. I dati sono senza schema, il che significa che non è necessario specificare quali chiavi esistono su ogni PFObject.

Tutti i dati Firebase Realtime Database vengono archiviati come oggetti JSON e non esiste un equivalente per PFObject. Devi semplicemente scrivere nella struttura ad albero JSON valori di tipi corrispondenti ai tipi JSON disponibili.

Di seguito è riportato un esempio di come salvare i migliori punteggi di un gioco.

Analizza
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
  }
}];
Per maggiori dettagli, consulta la guida Leggere e scrivere dati sulle piattaforme Apple.

Relazioni tra i dati

Un PFObject può avere una relazione con un altro PFObject: qualsiasi oggetto può utilizzare altri oggetti come valori.

In Firebase Realtime Database, le relazioni sono espresse meglio utilizzando strutture di dati piatte che suddividono i dati in percorsi distinti, in modo che possano essere scaricati in modo efficiente in chiamate separate.

Di seguito è riportato un esempio di come strutturare la relazione tra i post di un'app di blogging e i relativi autori.

Analizza
// 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]

Il risultato è il seguente layout dei dati.

{
  // 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"
    }
    ...
  }
}
Per ulteriori dettagli, consulta la guida Strutturare il database.

Lettura dei dati

In Parse puoi leggere i dati utilizzando l'ID di un oggetto Parse specifico o eseguire query utilizzando PFQuery.

In Firebase, i dati vengono recuperati collegando un ascoltatore asincrono a un riferimento di database. Il listener viene attivato una volta per lo stato iniziale dei dati e di nuovo quando i dati cambiano, quindi non dovrai aggiungere alcun codice per determinare se i dati sono cambiati.

Di seguito è riportato un esempio di come recuperare i punteggi di un determinato giocatore, in base all'esempio presentato nella sezione "Objects".

Analizza
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);
}];
Per maggiori dettagli sui tipi di ascoltatori di eventi disponibili e su come ordinare e filtrare i dati, consulta la guida Leggere e scrivere dati sulle piattaforme Apple.

Strategia di migrazione suggerita

Reinterpreta i tuoi dati

Firebase Realtime Database è ottimizzato per sincronizzare i dati in millisecondi su tutti i client collegati e la struttura di dati risultante è diversa da quella dei dati di Parse principali. Ciò significa che il primo passaggio della migrazione consiste nel valutare le modifiche richieste dai dati, tra cui:

  • Come mappare gli oggetti Parse ai dati di Firebase
  • Se hai relazioni principali-secondarie, come suddividere i dati in percorsi diversi in modo che possano essere scaricati in modo efficiente in chiamate separate.

Esegui la migrazione dei dati

Dopo aver deciso come strutturare i dati in Firebase, devi pianificare come gestire il periodo durante il quale la tua app deve scrivere in entrambi i database. Le opzioni disponibili sono:

Sincronizzazione in background

In questo scenario, hai due versioni dell'app: la vecchia versione che utilizza Parse e una nuova versione che utilizza Firebase. Le sincronizzazioni tra i due database sono gestite da Parse Cloud Code (da Parse a Firebase), con il codice che ascolta le modifiche su Firebase e le sincronizza con Parse. Prima di poter iniziare a utilizzare la nuova versione, devi:

  • Converti i dati di Parse esistenti nella nuova struttura Firebase e scrivili in Firebase Realtime Database.
  • Scrivi funzioni Cloud Code di Parse che utilizzano l'API REST di Firebase per scrivere le modifiche apportate ai dati di Parse dai vecchi client.Firebase Realtime Database
  • Scrivi ed esegui il deployment di codice che ascolta le modifiche su Firebase e le sincronizza con il database Parse.

Questo scenario garantisce una separazione chiara del codice vecchio e nuovo e mantiene i client semplici. I problemi di questo scenario sono la gestione di set di dati di grandi dimensioni nell'esportazione iniziale e l'assicurazione che la sincronizzazione bidirezionale non generi una ricorsione infinita.

Doppia scrittura

In questo scenario, scrivi una nuova versione dell'app che utilizza sia Firebase sia Parse, utilizzando Parse Cloud Code per sincronizzare le modifiche apportate dai vecchi client dai dati di Parse a Firebase Realtime Database. Quando un numero sufficiente di utenti ha eseguito la migrazione dalla versione dell'app solo Parse, puoi rimuovere il codice Parse dalla versione con doppia scrittura.

Questo scenario non richiede codice lato server. Gli svantaggi sono che i dati a cui non viene eseguito accesso non vengono sottoposti a migrazione e che le dimensioni dell'app aumentano con l'utilizzo di entrambi gli SDK.

Firebase Authentication

Firebase Authentication può autenticare gli utenti utilizzando password e provider di identità federati di uso comune come Google, Facebook e Twitter. Fornisce inoltre librerie UI per farti risparmiare sull'investimento significativo necessario per implementare e gestire un'esperienza di autenticazione completa per la tua app su tutte le piattaforme.

Per scoprire di più, consulta la documentazione di Firebase Authentication.

Differenze con Parse Auth

Parse fornisce una classe utente specializzata chiamata PFUser che gestisce automaticamente la funzionalità richiesta per la gestione degli account utente. PFUser è una sottoclasse di PFObject, il che significa che i dati utente sono disponibili in Analizza dati e possono essere estesi con campi aggiuntivi come qualsiasi altro PFObject.

Un FIRUser ha un insieme fisso di proprietà di base (un ID univoco, un indirizzo email principale, un nome e un URL di una foto) memorizzate nel database utente di un progetto separato. Queste proprietà possono essere aggiornate dall'utente. Non puoi aggiungere altre proprietà direttamente all'oggetto FIRUser, ma puoi archiviarle in Firebase Realtime Database.

Di seguito è riportato un esempio di come registrare un utente e aggiungere un altro campo per il numero di telefono.

Analizza
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"];
  }
}];

Strategia di migrazione suggerita

Esegui la migrazione degli account

Per eseguire la migrazione degli account utente da Parse a Firebase, esporta il database utente in un file JSON o CSV, quindi importa il file nel progetto Firebase utilizzando il comando auth:import dell'interfaccia a riga di comando di Firebase.

Innanzitutto, esporta il database utente dalla console Parse o dal database auto-in hosting. Ad esempio, un file JSON esportato dalla console Parse potrebbe avere il seguente aspetto:

{ // 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",
  ...
}

Trasforma poi il file esportato nel formato richiesto dalla CLI di Firebase. Utilizza il objectId degli utenti Parse come localId degli utenti Firebase. Inoltre, codifica in base64 i valori bcryptPassword di Parse e utilizzali nel campo passwordHash. Ad esempio:

{
  "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
        }
      ]
    }
  ]
}

Infine, importa il file trasformato con l'interfaccia a riga di comando di Firebase, specificando bcrypt come algoritmo di hashing:

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

Esegui la migrazione dei dati utente

Se memorizzi dati aggiuntivi per i tuoi utenti, puoi eseguirne la migrazione a Firebase Realtime Database utilizzando le strategie descritte nella sezione Migrazione dei dati. Se esegui la migrazione degli account utilizzando il flusso descritto nella sezione Migrazione degli account, i tuoi account Firebase avranno gli stessi ID dei tuoi account Parse, il che ti consentirà di eseguire facilmente la migrazione e la riproduzione di qualsiasi relazione basata sull'ID utente.

Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) è una soluzione di messaggistica multipiattaforma che ti consente di inviare messaggi e notifiche in modo affidabile e senza costi. Il compositore di notifiche è un servizio senza costi basato su Firebase Cloud Messaging che consente notifiche agli utenti target per gli sviluppatori di app mobile.

Per scoprire di più, consulta la documentazione di Firebase Cloud Messaging .

Differenze con le notifiche push di Parse

Ogni applicazione Parse installata su un dispositivo registrato per le notifiche ha un oggetto Installation associato, in cui vengono archiviati tutti i dati necessari per scegliere come target le notifiche. Installation è una sottoclasse di PFUser, il che significa che puoi aggiungere eventuali dati aggiuntivi alle istanze Installation.

Il generatore di notifiche fornisce segmenti di utenti predefiniti in base a informazioni quali app, versione dell'app e lingua del dispositivo. Puoi creare segmenti di utenti più complessi utilizzando eventi e proprietà Google Analytics per creare segmenti di pubblico. Per saperne di più, consulta la guida di assistenza relativa ai segmenti di pubblico. Queste informazioni sul targeting non sono visibili in Firebase Realtime Database.

Strategia di migrazione suggerita

Migrazione dei token dispositivo

Mentre Parse utilizza i token dispositivo APN per scegliere come target le installazioni per le notifiche, FCM utilizza i token di registrazione FCM mappati ai token dispositivo APN. Basta aggiungere l'SDK FCM alla tua app Apple per recuperare automaticamente un token FCM.

Migrazione dei canali agli FCM Argomenti

Se utilizzi i canali Parse per inviare notifiche, puoi eseguire la migrazione agli argomenti FCM, che forniscono lo stesso modello di publisher e sottoscrittori. Per gestire la transizione da Parse a FCM, puoi scrivere una nuova versione dell'app che utilizzi l'SDK Parse per annullare l'iscrizione ai canali Parse e l'SDK FCM per iscriverti ai temi FCM corrispondenti.

Ad esempio, se il tuo utente è iscritto all'argomento "Giganti", dovrai eseguire un'operazione simile alla seguente:

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
  }
}];

Con questa strategia, puoi inviare messaggi sia al canale Parse sia all'argomento corrispondenteFCM, supportando gli utenti sia della versione precedente sia di quella nuova. Quando un numero sufficiente di utenti avrà eseguito la migrazione dalla versione dell'app solo per Parse, potrai ritirare questa versione e iniziare a inviare utilizzando solo FCM.

Per scoprire di più, consulta la documentazione degli argomenti FCM.

Firebase Remote Config

Firebase Remote Config è un servizio cloud che ti consente di modificare il comportamento e l'aspetto della tua app senza richiedere agli utenti di scaricare un aggiornamento. Quando utilizzi Remote Config, crei valori predefiniti in-app che controllano il comportamento e l'aspetto della tua app. In seguito, potrai usare la console Firebase per eseguire l'override dei valori predefiniti in-app per tutti gli utenti o solo per alcuni segmenti della base utenti.

Firebase Remote Config può essere molto utile durante le migrazioni se vuoi testare diverse soluzioni ed essere in grado di spostare dinamicamente più clienti su un altro provider. Ad esempio, se hai una versione della tua app che utilizza sia Firebase sia Parse per i dati, puoi utilizzare una regola di percentile casuale per determinare quali client leggono da Firebase e aumentare gradualmente la percentuale.

Per scoprire di più su Firebase Remote Config, consulta la introduzione a Remote Config.

Differenze con Parse Config

Con Parse Config puoi aggiungere coppie chiave/valore alla tua app nella dashboard di Parse Config, quindi recuperare il PFConfig sul client. Ogni istanza PFConfig che ricevi è sempre immutabile. Quando in futuro recupererai un nuovo PFConfig dalla rete, non verrà modificata alcuna istanza PFConfig esistente, ma ne verrà creata una nuova e resa disponibile tramite currentConfig.

Con Firebase Remote Config crei valori predefiniti in-app per coppie chiave/valore che puoi eseguire l'override dalla console Firebase e puoi utilizzare regole e condizioni per fornire variazioni sull'esperienza utente della tua app a diversi segmenti della tua base utenti. Firebase Remote Config implementa una classe singleton che rende disponibili le coppie chiave/valore nella tua app. Inizialmente il singleton restituisce i valori predefiniti da te definiti in-app. Puoi recuperare dal server un nuovo insieme di valori in qualsiasi momento conveniente per la tua app; una volta recuperato il nuovo insieme, puoi scegliere quando attivarlo per rendere disponibili i nuovi valori per l'app.

Strategia di migrazione suggerita

Puoi passare a Firebase Remote Config copiando le coppie chiave/valore della configurazione di Parse nella Console Firebase e poi eseguendo il deployment di una nuova versione dell'app che utilizza Firebase Remote Config.

Se vuoi fare esperimenti sia con Parse Config sia con Firebase Remote Config, puoi eseguire il deployment di una nuova versione dell'app che utilizzi entrambi gli SDK finché un numero sufficiente di utenti non avrà eseguito la migrazione dalla versione solo Parse.

Confronto del codice

Analizza

[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;