將 Parse iOS 應用程式遷移至 Firebase

如果您是剖析使用者,且想尋找替代後端 服務解決方案 (Firebase) 可能會是 iOS 應用程式的最佳選擇。

本指南說明如何將特定服務整合至您的應用程式。適用對象 基本 Firebase 設定操作說明,請參閱 iOS+ 設定 指南。

Google Analytics

Google Analytics 是一款免費的應用程式成效評估解決方案,可針對應用程式的使用情況和 像是使用者參與度Analytics 已整合各項 Firebase 功能,可提供無限制 使用 Firebase SDK 定義最多 500 種不同事件的報表。

詳情請參閱 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 說明文件

與剖析資料的差異

物件

在「剖析」中,您會儲存包含鍵/值組合的 PFObject 或子類別 資料。資料為無結構定義,也就是說,您不需要指定鍵/值 都存在於各個 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
  }
}];
敬上 詳情請參閱 「在 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];
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"
    }
    ...
  }
}
敬上 詳情請參閱 建立資料庫結構 指南。

讀取資料

在剖析中,您可以使用特定剖析物件的 ID 讀取資料,或 使用 PFQuery 執行查詢。

在 Firebase 中,您可以將非同步事件監聽器附加至資料庫參照,藉此擷取資料。 監聽器的初始狀態就會觸發一次,並在資料變更時再次觸發。 因此您不必新增任何程式碼即可判斷資料是否變更。

以下範例說明如何根據 "Objects" 一節所示的範例。

剖析
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 Database 經過最佳化調整,能以毫秒為單位在所有連線間同步處理資料 而產生的資料結構與剖析核心資料不同。也就是說, 遷移的第一步是考量資料需要進行哪些變更,包括:

  • Parse 物件應如何對應至 Firebase 資料
  • 如果有父項/子項關係 如何劃分不同路徑的資料 能有效率地在不同呼叫中下載

遷移資料

決定好在 Firebase 中建構資料的方式後,接著就必須規劃要如何處理 期間,應用程式需要寫入兩個資料庫。可用的選項包括:

背景同步處理

在這個情境下,應用程式有兩個版本:使用 Parse 的舊版應用程式和新版本 以及使用 Firebase 的版本兩個資料庫之間的同步作業是由剖析 Cloud Code 處理 (剖析) 傳送至 Firebase),並使用程式碼來監聽 Firebase 中的變更,並將這些變更同步至 Parse。 開始使用新版本前,您必須:

  • 將現有的剖析資料轉換為新的 Firebase 結構,然後寫入 Firebase Realtime Database
  • 編寫剖析 Cloud Code 函式,使用 Firebase REST API 寫入 舊用戶端在剖析資料中所做的變更 Firebase Realtime Database
  • 編寫及部署程式碼,監聽 Firebase 中的變更,然後同步至剖析 資料庫

這個案例可確保您明確區分新舊程式碼,並讓用戶端保持簡單。 這種情況的挑戰,在於如何處理初始匯出中的大型資料集 雙向同步不會產生無限遞迴。

重複書寫

在這個情境中,您要使用 Firebase 和 Parse 編寫新版應用程式,並同時使用 Firebase 和 Parse 剖析 Cloud Code,將舊用戶端所做的變更從「剖析資料」同步到 Firebase Realtime Database。等到足夠人數從僅剖析版本的應用程式改用新平台後, 會從雙重寫入版本中移除剖析程式碼。

這種情況不需要任何伺服器端程式碼。缺點是資料不會 不會遷移,而應用程式大小會因為同時使用兩個 SDK 而增加。

Firebase Authentication

Firebase Authentication 可以使用密碼和熱門聯合識別資訊提供者來驗證使用者 例如 Google、Facebook 和 Twitter並提供 UI 程式庫 在應用程式中導入並維持完整的驗證體驗, 和所有平台。

詳情請參閱 Firebase Authentication 說明文件

剖析驗證的差異

Parse 提供名為 PFUser 的特殊使用者類別,可自動處理 管理使用者帳戶所需的功能PFUserPFObject,這表示「剖析資料」會提供使用者資料,且可使用 就像其他 PFObject 一樣。

FIRUser 具有一組固定的基本屬性,包括專屬 ID、主要電子郵件地址。 名稱和相片網址—儲存在個別專案的使用者資料庫中;這些屬性只要: 使用者。您無法直接將其他屬性新增至 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 CLI 的 auth:import 指令

首先,從剖析控制台或自行託管的使用者資料庫匯出 資料庫例如,從剖析控制台匯出的 JSON 檔案 如下所示:

{ // 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 要求的格式。 CLI使用剖析使用者的 objectId 做為 您 localId 的 Firebase 使用者。此外,Base64 會將 剖析中的 bcryptPassword 值,並用於 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 帳戶的 ID,方便您遷移及重現 使用者 ID 所建立的任何關係

Firebase Cloud Messaging

Firebase Cloud Messaging」(FCM) 是跨平台的訊息傳遞解決方案,提供可靠的服務 免費傳送訊息和通知。通知編輯器是免付費服務 是針對行動應用程式開發人員,開放指定使用者通知的 Firebase Cloud Messaging

詳情請參閱 Firebase Cloud Messaging 說明文件

剖析推播通知的差異

裝置上安裝的每個 Parse 應用程式都會在註冊通知時存取 Installation 物件,用來儲存目標通知所需的所有資料。 InstallationPFUser 的子類別,表示您可以 用於 Installation 例項的任何額外資料。

通知編輯器會根據應用程式、應用程式版本和裝置等資訊,提供預先定義的使用者區隔 語言。您可以使用 Google Analytics 事件和屬性建立更複雜的使用者區隔 建立目標對象查看目標對象 說明指南。這些指定目標資訊不會顯示在 Firebase Realtime Database 中。

建議遷移策略

遷移裝置權杖

剖析會使用 APN 裝置權杖來鎖定通知的安裝作業,FCM 使用的 FCM 註冊權杖會對應至 APN 裝置權杖。只要新增 FCM 即可。 Apple 應用程式導入 SDK,即可自動擷取 FCM 權杖

將頻道遷移至 FCM 個主題

如果您使用剖析頻道來傳送通知,可以遷移至 FCM 個主題, 相同的發布者/訂閱者模式如要處理從剖析到 FCM 的轉換,您可以編寫新版本 且使用 Parse SDK 取消訂閱剖析頻道的應用程式,以及 FCM SDK 訂閱 對應的 FCM 主題。

舉例來說,如果您訂閱了「巨人」topic,您可以進行如下操作:

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 是一項雲端服務,可讓您變更應用程式的行為和外觀 不必要求使用者下載應用程式更新。使用遠端設定時 控制應用程式行為和外觀的預設值。您之後可以使用 透過 Firebase 控制台,針對所有應用程式使用者或特定使用者族群覆寫應用程式內預設值。

如果您要在遷移作業中進行測試,Firebase Remote Config 就能派上用場 不同解決方案,以動態方式將更多客戶轉移至其他供應商。例如: 如果您的應用程式版本同時使用 Firebase 和 Parse 資料,您可以使用 來決定從 Firebase 讀取哪些用戶端,並逐漸提高百分比。

如要進一步瞭解Firebase Remote Config,請參閱 Remote Config 簡介

與剖析設定的差異

您可以透過剖析設定,透過剖析設定資訊主頁將鍵/值組合新增至應用程式, 在用戶端上擷取 PFConfig。您產生的每 PFConfig 個執行個體 get 一律無法變更日後從PFConfig 網路,因此不會修改任何現有的 PFConfig 執行個體,而只會修改 新建一個,並透過 currentConfig 提供。

有了 Firebase Remote Config,您就能為鍵/值組合建立應用程式內預設值,以便覆寫 ,您可以運用規則和條件,針對應用程式的 區分不同客群的使用者體驗Firebase Remote Config 會實作 可讓應用程式使用鍵/值組合的單例模式類別。單例模式一開始 您在應用程式內定義的預設值您可以隨時從伺服器擷取一組新的值 對應用程式的重要性成功擷取新資料集後,您就能選擇何時啟用 即可將新的值提供給應用程式。

建議遷移策略

只要複製 Parse 設定的鍵/值組合,即可移至 Firebase Remote Config ,然後部署使用 Firebase Remote Config 的新版應用程式。

如要同時測試剖析設定和 Firebase Remote Config,您可以 新版應用程式使用兩種 SDK,直到足夠的使用者從僅剖析版本遷移為止。

程式碼比較

剖析

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