將您的 Parse iOS 應用程式遷移到 Firebase

如果您是正在尋找替代後端即服務解決方案的 Parse 用戶,Firebase 可能是您 iOS 應用程式的理想選擇。

本指南介紹如何將特定服務整合到您的應用程式中。有關基本 Firebase 設定說明,請參閱iOS+ 設定指南。

Google分析

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分析

// 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 即時資料庫是一個 NoSQL 雲端託管資料庫。資料以 JSON 形式儲存並即時同步到每個連接的客戶端。

請參閱Firebase 即時資料庫文件以了解更多資訊。

與解析資料的差異

物件

在 Parse 中,您儲存一個PFObject或其子類,其中包含 JSON 相容資料的鍵值對。資料是無模式的,這表示您不需要指定每個PFObject上存在哪些鍵。

所有 Firebase 即時資料庫資料都儲存為 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 即時資料庫中,使用平面資料結構可以更好地表達關係,將資料分割到單獨的路徑中,以便可以在單獨的呼叫中有效地下載它們。

以下範例說明如何建立部落格應用程式中的貼文與其作者之間的關係。

解析
// 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 物件的 ID 讀取數據,或使用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 即時資料庫經過最佳化,可以在所有連接的用戶端之間以毫秒為單位同步數據,並且產生的數據結構與 Parse 核心數據不同。這意味著遷移的第一步是考慮資料需要進行哪些更改,包括:

  • 您的 Parse 物件應如何對應到 Firebase 數據
  • 如果您有父子關係,如何將資料拆分到不同的路徑,以便可以在單獨的呼叫中有效下載。

遷移您的數據

決定如何在 Firebase 中建立資料後,您需要規劃如何處理應用程式需要寫入兩個資料庫的時間段。您的選擇是:

後台同步

在這種情況下,您有兩個版本的應用程式:使用 Parse 的舊版本和使用 Firebase 的新版本。兩個資料庫之間的同步由 Parse Cloud Code(Parse 到 Firebase)處理,您的程式碼偵聽 Firebase 上的變更並將這些變更與 Parse 同步。在開始使用新版本之前,您必須:

  • 將現有的解析資料轉換為新的 Firebase 結構,並將其寫入 Firebase 即時資料庫。
  • 編寫 Parse Cloud 程式碼函數,使用 Firebase REST API 將舊客戶端在解析資料中所做的變更寫入 Firebase 即時資料庫。
  • 編寫和部署程式碼來偵聽 Firebase 上的變更並將其同步到 Parse 資料庫。

此方案可確保新舊程式碼完全分離,並使用戶端保持簡單。此場景的挑戰是在初始匯出中處理大數據集,並確保雙向同步不會產生無限遞歸。

雙寫

在這種情況下,您編寫一個同時使用 Firebase 和 Parse 的應用程式的新版本,並使用 Parse Cloud Code 將舊客戶端所做的變更從 Parse 資料同步到 Firebase 即時資料庫。當足夠多的人從僅 Parse 版本的應用程式遷移時,您可以從雙寫版本中刪除 Parse 程式碼。

此場景不需要任何伺服器端程式碼。它的缺點是不會遷移未存取的數據,並且使用這兩個 SDK 會增加應用程式的大小。

Firebase 身份驗證

Firebase 驗證可以使用密碼和流行的聯合身分提供者(例如 Google、Facebook 和 Twitter)對使用者進行身份驗證。它還提供 UI 庫,幫助您節省在所有平台上為您的應用程式實施和維護完整身份驗證體驗所需的大量投資。

請參閱Firebase 驗證文件以了解更多資訊。

與 Parse Auth 的差異

Parse 提供了一個名為PFUser的專用使用者類,它會自動處理使用者帳戶管理所需的功能。 PFUserPFObject的子類,這表示使用者資料在解析資料中可用,並且可以像任何其他PFObject一樣使用其他欄位進行擴充。

FIRUser具有一組固定的基本屬性 - 唯一 ID、主要電子郵件地址、姓名和照片 URL - 儲存在單獨項目的使用者資料庫中;這些屬性可以由使用者更新。您不能直接向FIRUser物件新增其他屬性;相反,您可以將其他屬性儲存在 Firebase 即時資料庫中。

以下範例說明如何註冊使用者並新增其他電話號碼欄位。

解析
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 CLI 的auth:import命令將該文件匯入到您的 Firebase 專案中。

首先,從 Parse 控制台或自託管資料庫匯出使用者資料庫。例如,從 Parse 控制台匯出的 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 所需的格式。使用 Parse 使用者的objectId作為 Firebase 使用者的localId 。此外,對來自 Parse 的bcryptPassword值進行 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 CLI 導入轉換後的文件,指定 bcrypt 作為雜湊演算法:

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

遷移用戶數據

如果您要為使用者儲存其他數據,則可以使用資料遷移部分中所述的策略將其遷移到 Firebase 即時資料庫。如果您使用帳戶遷移部分中所述的流程遷移帳戶,則您的 Firebase 帳戶具有與 Parse 帳戶相同的 ID,從而使您可以輕鬆遷移和重現由使用者 ID 鍵入的任何關係。

Firebase 雲端訊息傳遞

Firebase Cloud Messaging (FCM) 是一種跨平台訊息傳遞解決方案,可讓您免費可靠地傳遞訊息和通知。通知編輯器是基於 Firebase Cloud Messaging 建置的免費服務,可為行動應用程式開發者提供有針對性的使用者通知。

請參閱Firebase 雲端訊息傳遞文件以了解更多資訊。

與解析推播通知的差異

安裝在註冊通知的裝置上的每個 Parse 應用程式都有一個關聯的Installation對象,您可以在其中儲存定位通知所需的所有資料。 InstallationPFUser的子類,這表示您可以將所需的任何其他資料新增至Installation實例。

通知編輯器根據應用程式、應用程式版本和裝置語言等資訊提供預先定義的使用者細分。您可以使用 Google Analytics 事件和屬性來建立更複雜的使用者細分來建立受眾群體。請參閱觀眾幫助指南以了解更多資訊。這些定位資訊在 Firebase 即時資料庫中不可見。

建議的遷移策略

遷移設備令牌

Parse 使用 APNs 設備令牌來定位通知安裝,而 FCM 使用對應到 APNs 設備令牌的 FCM 註冊令牌。只需將 FCM SDK 新增到您的 Apple 應用程式中,它就會自動取得 FCM 令牌

將頻道移轉到 FCM 主題

如果您使用 Parse 通道發送通知,則可以遷移到 FCM 主題,它提供相同的發布者-訂閱者模型。要處理從 Parse 到 FCM 的轉換,您可以編寫新版本的應用程序,使用 Parse SDK 取消訂閱 Parse 頻道,並使用 FCM SDK 訂閱相應的 FCM 主題。

例如,如果您的使用者訂閱了「Giants」主題,您將執行以下操作:

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 遠端配置

Firebase Remote Config 是一項雲端服務,可讓您變更應用程式的行為和外觀,而無需使用者下載應用程式更新。使用遠端配置時,您可以建立應用程式內預設值來控制應用程式的行為和外觀。然後,您可以稍後使用 Firebase 控制台覆寫所有應用程式使用者或使用者群細分的應用程式內預設值。

當您想要測試不同的解決方案並能夠動態地將更多客戶端轉移到不同的提供者時,Firebase Remote Config 在遷移過程中會非常有用。例如,如果您的應用程式版本同時使用 Firebase 和 Parse 來獲取數據,則可以使用隨機百分位規則來確定哪些客戶端從 Firebase 讀取數據,並逐漸增加百分比。

要了解有關 Firebase 遠端配置的更多信息,請參閱遠端設定簡介

與解析配置的差異

使用 Parse 配置,您可以在 Parse Config Dashboard 上將鍵/值對新增至您的應用程序,然後在客戶端上取得PFConfig 。您獲得的每個PFConfig實例始終是不可變的。當您將來從網路檢索新的PFConfig時,它不會修改任何現有的PFConfig實例,而是建立一個新實例並透過currentConfig使其可用。

借助 Firebase Remote Config,您可以為鍵/值對建立應用程式內預設值,您可以從 Firebase 控制台覆寫這些預設值,並且可以使用規則和條件為不同的使用者群組提供不同的應用程式使用者體驗。 Firebase Remote Config 實作了一個單例類,使鍵/值對可供您的應用程式使用。最初,單例會傳回您在應用程式內定義的預設值。您可以在您的應用程式方便的任何時間從伺服器取得一組新的值;成功取得新集後,您可以選擇何時啟動它以使新值可供應用程式使用。

建議的遷移策略

您可以透過將 Parse 配置的鍵/值對複製到 Firebase 控制台,然後部署使用 Firebase Remote Config 的新版本應用程式來遷移到 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;