原文:Firebase Remote Config Tutorial for iOS
做者:Todd Kerpelman
譯者:kmyhyjavascript
在你發佈應用程序的時候,app 各個方面都已經完美了嗎?你永遠不須要碰其它代碼,由於不管什麼東西你一次就能作對?css
哈,我作不到。java
做爲一名成功的 app 開發者,一般須要對 app 進行頻繁的更改。有時,這些更改是增長功能或者 bug 的修復。可是,有時,最有關鍵的修改只是一行代碼而已,好比調整一下文字或者在一個塔式防護遊戲中削弱一個強大的單元。ios
雖然這些修改很簡單,可是發佈它卻要花一兩天的時間。若是你只須要作一些調整,但不須要經歷這整個過程就太好了。web
Firebase 遠程配置提供了這種能力。在這篇基於 Firebase 的 iOS 遠程配置教程中,你將使用 Planet Tour app 來學習如何修改文本、顏色和其餘行爲,而無需發佈新版本!掌握了它之後,你將學到更強大的功能,面向不一樣用戶交付不一樣內容。swift
前提條件:這篇基於 Firebase 的 iOS 遠程配置教程假設您安裝了 cocoaPods 並對它有必定的瞭解。若是不是這樣,請查看咱們的 CocoaPods 教程。瀏覽器
在本教程的頂部或底部有下載連接,請首先下載。解壓並運行開始項目。你能夠滑動以查看不一樣的行星,點擊每個行星來得到一些(大部分是準確的)詳情信息。緩存
你剛剛下載的 app 是 Planet Tour APP 公司製做的,它一直都很正常,直到有一天,來自市場營銷的格雷格決定,行星之旅變成綠色的配色方案,以慶祝地球日。bash
若是你打開 AppConstants.swift,你會發現這很容易搞定。有一個 appPrimaryColor變量,修改它會同時改變到許多文本標籤的顏色。將改變推給用戶須要發佈一個新版本,將它提交給應用商店,經過審批,而後等用戶在地球日以前下載它。一旦地球日結束,你必須從新作整個過程,恢復以前的樣子。服務器
若是能從雲端修改這些值,那就行了。
要將 AppConstants 中的硬編碼值修改成遠程配置,須要在 Firebase 控制檯中新建一個項目,將它和 Planet Tour app 關聯,而後安裝 Firebase 遠程配置庫。
步驟以下:
將項目名稱命名爲 Planet Tour,選中你所在的區域,而後點擊 Create Project。
點擊 Add Firebase to your iOS app:
設置項目的 bundle ID 爲 com.razeware.Planet-Tour —— 保持 App Store ID 字段留空,而後點擊 Register App:
點擊 Download 下載一個 GoogleServices-info.plist 文件:
瀏覽器會下載一個 GoogleServices-info.plist 文件。將它拖進你的 Xcode 項目中。確保選擇 Copy Items if Needed。
不停點擊向導中的 Continue 按鈕。別怕,接下來你將會看到這些指引。
編輯 Podfile 文件爲:
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'
target 'Planet Tour' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for Planet Tour
pod 'Firebase/Core'
pod 'Firebase/RemoteConfig'
end
執行 pod install, 用 Xcode 打開 Planet Tour.xcworkspace。
打開 AppDelegate.swift.在 import UIKit 以後添加:
import Firebase
而後,在 application(_:didFinishLaunchingWithOptions:) 的 return 語句以前添加:
FirebaseApp.configure()
這個方法檢查剛纔安裝的兩個庫,並使用你添加在 GoogleServices-info 中的常量來初始化。遠程配置庫如今知道要去 internet 上查找新值的正確位置。
Build & run,app 和以前同樣,但你從控制檯中會看到一些以前沒有的調試信息。
恭喜你!你已經安裝好了遠程配置!如今你能夠在接下來的教程中使用它了!
很是簡單,遠程配置經過相似於在雲端的 [String:Any] 字典來工做。當你的應用程序啓動時,它會從雲中抓取任何可能須要的新值,而後將它們應用到你可能指定爲默認值的任何舊值之上。
使用遠程配置的通常流程是:
有一點要注意,獲取到的這些新值一般是你提供的默認值的子集。你能夠將應用程序中幾乎任何硬編碼的字符串、數字或布爾值,打包在一塊兒到遠程配置。這讓你可以在之後靈活地修改應用程序的許多地方,同時保持網絡調用小而美。
理論課完了,來點實際的!
首先,打開 Planet Tour 項目的 Utilities 文件夾,用右鍵新建文件。選擇 Swift 文件,文件名就叫作 RCValues.swift,將它建立在 Xcode 推薦的默認目錄下。
在文件中加入:
import Firebase
class RCValues { static let sharedInstance = RCValues() private init() { loadDefaultValues() } func loadDefaultValues() { let appDefaults: [String: Any?] = [ "appPrimaryColor" : "#FBB03B" ] RemoteConfig.remoteConfig().setDefaults(appDefaults as? [String: NSObject]) } }
這裏,使用了單例模式。在 loadDefaultValues() 中,傳遞給遠程配置一對鍵值對,充當默認值。這裏只提供了一個值,但別急,後面會添加更多值。
而後,請求遠程配置從雲端返回新值。在 loadDefaultValues() 方法下面添加方法:
func fetchCloudValues() {
// 1
// WARNING: Don't actually do this in production!
let fetchDuration: TimeInterval = 0
RemoteConfig.remoteConfig().fetch(withExpirationDuration: fetchDuration) { status, error in
if let error = error {
print("Uh-oh. Got an error fetching remote values \(error)")
return
}
// 2
RemoteConfig.remoteConfig().activateFetched()
print("Retrieved values from the cloud!")
}
}
這裏作了些什麼?
默認狀況下,遠程配置將緩存從雲中檢索的任何值大約12小時。在一個生產應用中,這可能很好。可是,當你在進行開發或在線使用這篇 Firebase 遠程配置教程時,這可能會讓測試新值變得很是麻煩。因此,你要指定 fetchDuration 爲 0,表示永遠不使用緩存數據。
在 completion 閉包中,當即激活抓取到的值 —— 即告訴遠程配置將新值替換掉舊值。
在 init() 方法中調用新方法:
fetchCloudValues()
您在開始添加的代碼會有一些問題。遠程配置庫對客戶端流量有控制,確保你沒法頻繁地 ping 服務。將 fetchDuration 設爲 0 會和這個控制發生衝突,你的庫將中止抓取值。
能夠打開開發者模式以解決這個問題。在 fetchCloudValues() 下面添加方法:
func activateDebugMode() {
if let debugSettings = RemoteConfigSettings(developerModeEnabled: true) {
RemoteConfig.remoteConfig().configSettings = debugSettings
}
}
將開發者模式設置爲 true,告訴遠程配置忽略客戶端流量控制。對於開發過程,或者在 10 人如下的測試團隊來講,這是沒問題的。可是,若是你把這款應用發佈到公衆面前,你會有數百萬的粉絲,你很快就會引發服務器端的節流閥,遠程配置會中止工做。這就是你首先要有一個客戶端節流閥的緣由。
在將 app 推到生產以前,確保禁用開發者模式,並將 fetchDuration 設置爲更合理的時間,好比43200,即 12小時。
最後,在 fetchDuration 變量的聲明語句以後添加:
activateDebugMode()
這將開啓 debug 模式,避免發生服務端節流控制問題。
打開 AppDelegate.swift,在application(_:didFinishLaunchingWithOptions:) 的 FirebaseApp.configure() 之下添加:
let _ = RCValues.sharedInstance
Build & run,你會看到控制檯中輸出:
Retrieved values from the cloud!
如今你下載了這些值,來將它們輸出到控制檯。打開 RCValues.swift。在 fetchCloudValues() 的 「Retrieved values from the cloud」 一行後面添加:
print("Our app's primary color is \(RemoteConfig.remoteConfig().configValue(forKey: "appPrimaryColor"))")
這句代碼將打印 appPrimaryColor 的值。
Build & run。你會開到:
Our app's primary color is <FIRRemoteConfigValue: 0x61000003ece0>
這很好,但你想要一個字符串值。
遠程配置將檢索到的值當成 RemoteConfigValue 對象,你能夠將其看做是包含底層數據的容器,後者在內部表示爲 utf8 編碼的字符串。你幾乎不會直接使用這個對象。相反,你將調用 numberValue 或 boolValue 之類的輔助方法來檢索你想要的實際值。
將剛纔那句替換成:
let appPrimaryColorString = RemoteConfig.remoteConfig()
.configValue(forKey: "appPrimaryColor")
.stringValue ?? "undefined"
print("Our app's primary color is \(appPrimaryColorString)")
Build & run。此次你會看到:
Our app's primary color is #FBB03B
這會更方便一點。遠程配置提供了你以前指定的默認值。
如今你可以從遠程配置拿到正確的值了,嘗試在雲端提供新值吧。
打開 Firebase 控制檯,點擊頂部左邊的 Remote Config (在 Grow 之下)。點擊 Add your first parameter。在表單中,將 key 設爲 appPrimaryColor ,而值則設置爲市場部 Greg 最愛的新綠色 #36C278。
點擊 Add Parameter, 而後點擊 Publish Changes (兩次) 去刷新修改。
Build & run 。
你在控制檯中會看到:
Our app’s primary color is #36C278
成功了!你已經修改了雲端的值!
如今,來將新值和 app 關聯起來。
首先,加一個枚舉來保存 key 值。使用常量字符串做爲 key 是一種災難,或者你起碼得花一下午的時間來尋找一個神祕的 bug,由於你輸錯了一個 key。經過使用enum,Xcode能夠在編譯時捕捉錯誤,而不是運行時。
打開 RCValues.swift 在類定義之上加入:
enum ValueKey: String {
case appPrimaryColor
}
而後,修改 loadDefaultValues() 方法,使用枚舉代替常量字符串:
let appDefaults: [String: Any?] = [
ValueKey.appPrimaryColor.rawValue : "#FBB03B"
]
在 RCValue 中添加一個工具方法,它將一個 ValueKey 做爲參數,而後根據這個字符串從遠程配置返回一個 UIColor。
func color(forKey key: ValueKey) -> UIColor {
let colorAsHexString = RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? "#FFFFFF"
let convertedColor = UIColor(colorAsHexString)
return convertedColor
}
最後,將 app 中原來使用 AppConstants 常量的地方替換成 RCValues 的工具方法。
有 3 個地方須要改:
打開 ContainerViewController.swift 修改 updateBanner() 方法中的:
bannerView.backgroundColor = AppConstants.appPrimaryColor
爲:
bannerView.backgroundColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
打開 GetNewsletterViewController.swift 將 updateSubmitButton() 方法中的:
submitButton.backgroundColor = AppConstants.appPrimaryColor
修改成:
submitButton.backgroundColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
打開 PlanetDetailViewController.swift 將 updateLabelColors() 方法中的:
nextLabel.textColor = AppConstants.appPrimaryColor
修改成:
nextLabel.textColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
爲了完美,打開 AppConstants.swift and delete the following:
static let appPrimaryColor = UIColor(rgba: "#FBB03B")
再見了,硬編碼…
Build& run。你會發現整個 app 變成綠色的了:
當這些新值被應用時,你不能作過多控制。當第一次運行 app 時,可能會看到主菜單是默認的橙色,可是當新值從雲中加載後,你就會看到行星詳情中顯示的新綠色。
這可能會讓用戶感到困惑。在這個例子裏,你只是修改了一些標籤的顏色,可是若是你的 app 改變了可以影響到它的行爲的文本或值怎麼辦呢?若是用戶正在使用的過程當中,他們會感到困惑。
有不少方法能夠解決這個問題,可是最簡單的方法是建立一個加載頁面。在本教程中,它已經建立好了一部分。
首先,讓加載屏幕成爲 app 的 inital View controller。打開 Main.storyboard。右鍵,從導航控制器拖一條線到 Waiting View Controller —— 它是一個黑背景的視圖控制器,你也能夠經過 outline 視圖來操做,這樣可能更容易。從彈出菜單中選擇 root view controller:當你的應用加載時,這將使你的加載屏幕成爲初始屏幕。
如今,爲遠程配置加載完成後時添加跳轉到主菜單的邏輯。
打開 RCValues.swift, 在 sharedInstance 屬性後添加:
var loadingDoneCallback: (() -> Void)? var fetchComplete = false
接着,將 fetchCloudValues() 改爲:
func fetchCloudValues() {
// WARNING: Don't actually do this in production!
let fetchDuration: TimeInterval = 0
activateDebugMode()
RemoteConfig.remoteConfig().fetch(withExpirationDuration: fetchDuration) { [weak self] status, error in
if let error = error {
print ("Uh-oh. Got an error fetching remote values \(error)")
return
}
RemoteConfig.remoteConfig().activateFetched()
print ("Retrieved values from the cloud!")
let appPrimaryColorString = RemoteConfig.remoteConfig()
.configValue(forKey: "appPrimaryColor")
.stringValue ?? "undefined"
print("Our app's primary color is \(appPrimaryColorString)")
self?.fetchComplete = true
self?.loadingDoneCallback?()
}
}
這裏,當請求完成後,將 fetchComplete 設置爲 true。最後調用回調函數,通知監聽者遠程配置值加載完成。這能夠用於通知加載屏幕解散本身。
打開 WaitingViewController.swift 添加方法:
func startAppForReal() {
performSegue(withIdentifier: "loadingDoneSegue", sender: self)
}
將 viewDidLoad() 修改成:
override func viewDidLoad() {
super.viewDidLoad()
if RCValues.sharedInstance.fetchComplete {
startAppForReal()
}
RCValues.sharedInstance.loadingDoneCallback = startAppForReal
}
當全部值加載完後,由 RCValue 調用 startAppForReal()。同時還增長了一個判斷,防止 RCValue 網絡調用有時會在等待屏加載以前完成。這應該不會發生,但這種預防措施無傷大雅。
Todd 編碼原則:在代碼註釋中添加一句「這一點不該該發生」,這將使事情在未來的某個時候真的會發生。
Build & run。你會看到屏幕上出現一個短暫的等待,這取決於你的網速,再跳到其它界面。若是你改變 app 的主顏色值並從新啓動應用程序,新顏色將在你的應用程序中正確顯示。記得點擊在 Firebase 控制檯中點擊 Publish Changes。
你曾經將一個 AppConstants 常量修改爲 RCValue,如今來修改其它常量!打開 RCValues.swift 將 ValueKey 修改成:
enum ValueKey: String {
case bigLabelColor
case appPrimaryColor
case navBarBackground
case navTintColor
case detailTitleColor
case detailInfoColor
case subscribeBannerText
case subscribeBannerButton
case subscribeVCText
case subscribeVCButton
case shouldWeIncludePluto
case experimentGroup
case planetImageScaleFactor
}
而後,修改 loadDefaultValues():
func loadDefaultValues() {
let appDefaults: [String: Any?] = [
ValueKey.bigLabelColor.rawValue: "#FFFFFF66",
ValueKey.appPrimaryColor.rawValue: "#FBB03B",
ValueKey.navBarBackground.rawValue: "#535E66",
ValueKey.navTintColor.rawValue: "#FBB03B",
ValueKey.detailTitleColor.rawValue: "#FFFFFF",
ValueKey.detailInfoColor.rawValue: "#CCCCCC",
ValueKey.subscribeBannerText.rawValue: "Like Planet Tour?",
ValueKey.subscribeBannerButton.rawValue: "Get our newsletter!",
ValueKey.subscribeVCText.rawValue: "Want more astronomy facts? Sign up for our newsletter!",
ValueKey.subscribeVCButton.rawValue: "Subscribe",
ValueKey.shouldWeIncludePluto.rawValue: false,
ValueKey.experimentGroup.rawValue: "default",
ValueKey.planetImageScaleFactor.rawValue: 0.33
]
RemoteConfig.remoteConfig().setDefaults(appDefaults as? [String: NSObject])
}
添加 3 個工具方法,以檢索顏色以外的值:
func bool(forKey key: ValueKey) -> Bool {
return RemoteConfig.remoteConfig()[key.rawValue].boolValue
}
func string(forKey key: ValueKey) -> String {
return RemoteConfig.remoteConfig()[key.rawValue].stringValue ?? ""
}
func double(forKey key: ValueKey) -> Double {
if let numberValue = RemoteConfig.remoteConfig()[key.rawValue].numberValue {
return numberValue.doubleValue
} else {
return 0.0
}
}
而後,將 app 中使用到 AppConstants 的每一部分都替換成對應的 RCValues 調用。。
整個 app 須要有 9 處修改:
打開 ContainerViewController.swift 修改 updateNavigationColors() 方法爲:
func updateNavigationColors() {
navigationController?.navigationBar.tintColor = RCValues.sharedInstance.color(forKey: .navTintColor)
}
修改 updateBanner() 方法:
func updateBanner() {
bannerView.backgroundColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
bannerLabel.text = RCValues.sharedInstance.string(forKey: .subscribeBannerText)
getNewsletterButton.setTitle(RCValues.sharedInstance.string(forKey: .subscribeBannerButton), for: .normal)
}
修改 GetNewsletterViewController.swift 的 updateText()方法:
func updateText() {
instructionLabel.text = RCValues.sharedInstance.string(forKey: .subscribeVCText)
submitButton.setTitle(RCValues.sharedInstance.string(forKey: .subscribeVCButton), for: .normal)
}
將 PlanetDetailViewController.swift 的 updateLabelColors() 方法的這句:
nextLabel.textColor = AppConstants.detailInfoColor
修改成:
nextLabel.textColor = RCValues.sharedInstance.color(forKey: .detailInfoColor)
將
planetNameLabel.textColor = AppConstants.detailTitleColor
修改成
planetNameLabel.textColor = RCValues.sharedInstance.color(forKey: .detailTitleColor)
將 PlanetsCollectionViewController.swift 中 customizeNavigationBar() 方法的這句:
navBar.barTintColor = AppConstants.navBarBackground
修改成:
navBar.barTintColor = RCValues.sharedInstance.color(forKey: .navBarBackground)
將 collectionView(_:cellForItemAt:) 的這句:
cell.nameLabel.textColor = AppConstants.bigLabelColor
修改成:
cell.nameLabel.textColor = RCValues.sharedInstance.color(forKey: .bigLabelColor)
將 SolarSystem.swift 的 init() 的這句:
if AppConstants.shouldWeIncludePluto {
修改成:
if RCValues.sharedInstance.bool(forKey: .shouldWeIncludePluto) {
將 calculatePlanetScales()的這句:
scaleFactors[i] = pow(ratio, AppConstants.planetImageScaleFactor)
修改成:
scaleFactors[i] = pow(ratio, RCValues.sharedInstance.double(forKey: .planetImageScaleFactor))
噓,要改的地方太多了,但整個 app 都修改好了。若是你想確認一下,能夠搜索 AppConstants —— 你只能搜到一個結果了,那就是結構體本身的定義:
若是想真正確認,刪除 AppConstants 文件。app 仍然能經過編譯,不會出現報錯。
如今你的 app 已經徹底封裝成遠程配置的了,除了將顏色修改成 Greg 很是喜歡的綠色以外,你能夠作更多的改變。
打開 Firebase 控制檯。進入 Remote Config,點擊 Add Parameter。設置 key 爲 navBarBackground ,value 設置爲 #35AEB1 ,而後點擊 Add Parameter。一樣,設置 navTintColor 爲 #FFFFFF。點擊 Publish Changes,將改變推到 app。
作完以後的控制檯是這個樣子:
發佈修改,Build & run.
你的 app 是這樣的:
請本身玩一下!修改其它值,隨便改下文字。看看什麼比較時髦……或者花哨……你能夠想出什麼樣的顏色組合。
當你玩夠之後,回到本教程,由於你還有一個嚴重問題須要處理。
在丹麥,狀況緊急!雖然世界上大多數人都認爲冥王星不是行星,但北歐保護冥王星協會,一個由狂熱的冥王星迷組成的組織,頑固地堅持冥王星做爲一顆行星存在,並應當將它放到 Planet Tour App 中。在你閱讀本文的同時,哥本哈根的街道上排滿了抗議的人羣!怎麼辦?
從新發佈一個 App,則會激怒另外一羣人……
好吧,使用遠程配置,這好像不是太難!你能夠將 shouldWeIncludePluto 設爲 true。等一下,這會改變全部用戶的設置,而不只僅是北歐!怎樣才能基於不一樣的地區下發不一樣的設置?
答案是 Conditions!
遠程配置比起簡單的雲端字典來講更加智能,那就是根據不一樣的人羣發佈不一樣的設置。你能夠利用這個特性容許北歐用戶從新接回它們的冥王星。
首先,打開 Firebase 控制檯,在 Remote Config 面板中,點擊 Add Parameter 添加一個新參數。
key 輸入 shouldWeIncludePluto。
點擊 value 欄旁邊的 Add value for condition 下拉框。選擇 Define New Condition。
若是你對 「Experiment with this parameter」 選項感到迷惑,你能夠看一下咱們的 A/B 測試教程,它恰好延展了這部份內容。
在對話框中,給新條件命名爲 Pluto Fans。
在下拉框中,選擇 Device Region / Country。
在國家列表中,選擇 Denmark, Sweden, Norway, Iceland, 和 Finland。
點擊 Create Condition。
而後,在 Value for Pluto Fans 欄,輸入值 true。在 Default value 欄輸入 false。
最後,點擊 Add Parameter,再點擊 Publish Changes。
運行程序,假設你沒有在這些北半球國家,你仍然不能在行星列表中看見冥王星。若是你想體驗一下北歐用戶,我建議你買一張到哥本哈根的機票,買一部丹麥版的 iPhone,而後打開 App,順便來一塊薰鮭魚單片三明治。
有一個更經濟的作法(同時飛行時差反應也更少)是,打開設備後模擬器上的設置程序。選擇 General > Language & Region > Region > Denmark (或其它北歐國家):
這比飛到哥本哈根要便宜得多了,但同時也少了許多樂趣。
運行程序,此次你能夠看見冥王星和別的行星列在一塊兒。呼,避免了一塊兒國際糾紛!
還有另外一種不須要修改模擬器設置的方法,在 Xcode 中在 Run 按鈕上 Option + 單擊。在對話框中,單擊 Options 面板,而後在 Application Region 菜單中選擇一個合適的國家。
你能夠用底部的 Download Material 鏈接下載最終項目。可是請注意,你仍然須要在 Firabase 控制檯中建立項目,並將你的 GoogleServices-info.plist 文件拖到項目中。
有不少特性尚未來得及展現。經過將值下發給隨機的用戶組,你能夠用遠程配置來進行 A/B 測試,或者逐步將新功能推廣到其餘地區。你還能夠將不一樣的數據集下發給經過 Firebase Analytics 識別出的某個用戶組,實現某種定製化效果。若是你想進一步瞭解,請閱讀這裏以及下一篇教程。
經過遠程配置能讓你實現許多功能。若是你在開發遊戲,若是玩家以爲難度太低或太高,用它來調整遊戲玩法是一種好辦法。還能夠用它來實現「每日提醒」之類的功能。甚至能夠用它來實驗不一樣的按鈕和標籤文本,看看哪一種可以讓用戶體驗最好。在你的 App 中試試吧,看看你能改變些什麼?
若是你有任何關於 Firebase 遠程配置的問題或建議(或者有更好的 Planet Tour 配色方案),請在論壇中留言!