Firebase iOS 远程配置教程

原文:Firebase Remote Config Tutorial for iOS
作者:Todd Kerpelman
译者:kmyhy

在你发布应用程序的时候,app 各个方面都已经完美了吗?你永远不需要碰其它代码,因为无论什么东西你一次就能做对?

哈,我做不到。

作为一名成功的 app 开发者,通常需要对 app 进行频繁的更改。有时,这些更改是增加功能或者 bug 的修复。但是,有时,最有关键的修改只是一行代码而已,比如调整一下文字或者在一个塔式防御游戏中削弱一个强大的单元。

虽然这些修改很简单,但是发布它却要花一两天的时间。如果你只需要做一些调整,但不需要经历这整个过程就太好了。

Firebase 远程配置提供了这种能力。在这篇基于 Firebase 的 iOS 远程配置教程中,你将使用 Planet Tour app 来学习如何修改文本、颜色和其他行为,而无需发布新版本!掌握了它以后,你将学到更强大的功能,面向不同用户交付不同内容。

前提条件:这篇基于 Firebase 的 iOS 远程配置教程假设您安装了 cocoaPods 并对它有一定的了解。如果不是这样,请查看我们的 CocoaPods 教程

开始

在本教程的顶部或底部有下载链接,请首先下载。解压并运行开始项目。你可以滑动以查看不同的行星,点击每一个行星来获得一些(大部分是准确的)详情信息。

你刚刚下载的 app 是 Planet Tour APP 公司制作的,它一直都很正常,直到有一天,来自市场营销的格雷格决定,行星之旅变成绿色的配色方案,以庆祝地球日。

如果你打开 AppConstants.swift,你会发现这很容易搞定。有一个 appPrimaryColor变量,修改它会同时改变到许多文本标签的颜色。将改变推给用户需要发布一个新版本,将它提交给应用商店,通过审批,然后等用户在地球日之前下载它。一旦地球日结束,你必须重新做整个过程,恢复之前的样子。

如果能从云端修改这些值,那就好了。

安装远程配置库

要将 AppConstants 中的硬编码值修改为远程配置,需要在 Firebase 控制台中新建一个项目,将它和 Planet Tour app 关联,然后安装 Firebase 远程配置库。

步骤如下:

  1. 打开网址 firebase.google.com/console
  2. 点击 Create New Project.
  3. 将项目名称命名为 Planet Tour,选中你所在的区域,然后点击 Create Project。

  4. 点击 Add Firebase to your iOS app:

  5. 设置项目的 bundle ID 为 com.razeware.Planet-Tour —— 保持 App Store ID 字段留空,然后点击 Register App:

  6. 点击 Download 下载一个 GoogleServices-info.plist 文件:

  7. 浏览器会下载一个 GoogleServices-info.plist 文件。将它拖进你的 Xcode 项目中。确保选择 Copy Items if Needed。

  8. 不停点击向导中的 Continue 按钮。别怕,接下来你将会看到这些指引。

  9. 在 Xcode 中关闭 Planet Tour。
  10. 打开终端窗口,进入项目目录,输入 pod init ,创建一个 Podfile 文件。
  11. 编辑 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
  12. 执行 pod install, 用 Xcode 打开 Planet Tour.xcworkspace。

  13. 打开 AppDelegate.swift.在 import UIKit 之后添加:

    import Firebase

    然后,在 application(_:didFinishLaunchingWithOptions:) 的 return 语句之前添加:

    FirebaseApp.configure()

    这个方法检查刚才安装的两个库,并使用你添加在 GoogleServices-info 中的常量来初始化。远程配置库现在知道要去 internet 上查找新值的正确位置。

Build & run,app 和之前一样,但你从控制台中会看到一些之前没有的调试信息。
恭喜你!你已经安装好了远程配置!现在你可以在接下来的教程中使用它了!

远程配置是如何工作的

非常简单,远程配置通过类似于在云端的 [String:Any] 字典来工作。当你的应用程序启动时,它会从云中抓取任何可能需要的新值,然后将它们应用到你可能指定为默认值的任何旧值之上。

使用远程配置的一般流程是:

  1. 为你有可能在将来改变的任何值提供远程配置的默认值。
  2. 从云端抓取新值。它们会以一种“缓存的持有模式”保存在您的设备上。
  3. “激活”抓取到的值。这时,会将获取的值应用到原有默认值之上。
  4. 查询远程配置。如果云端有值,远程配置会根据指定的 key 给你云端的值,否则会返回默认值。

有一点要注意,获取到的这些新值通常是你提供的默认值的子集。你可以将应用程序中几乎任何硬编码的字符串、数字或布尔值,打包在一起到远程配置。这让你能够在以后灵活地修改应用程序的许多地方,同时保持网络调用小而美。

理论课完了,来点实际的!

使用远程配置

首先,打开 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!")
  }
}

这里做了些什么?

  1. 默认情况下,远程配置将缓存从云中检索的任何值大约12小时。在一个生产应用中,这可能很好。但是,当你在进行开发或在线使用这篇 Firebase 远程配置教程时,这可能会让测试新值变得非常麻烦。所以,你要指定 fetchDuration 为 0,表示永远不使用缓存数据。

  2. 在 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 的外观

现在,来将新值和 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 个地方需要改:

  1. 打开 ContainerViewController.swift 修改 updateBanner() 方法中的:

    bannerView.backgroundColor = AppConstants.appPrimaryColor
    

    为:

    bannerView.backgroundColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
    
  2. 打开 GetNewsletterViewController.swift 将 updateSubmitButton() 方法中的:

    submitButton.backgroundColor = AppConstants.appPrimaryColor
    

    修改为:

    submitButton.backgroundColor = RCValues.sharedInstance.color(forKey: .appPrimaryColor)
    
  3. 打开 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。

修改 App 的其它部分

你曾经将一个 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 处修改:

  1. 打开 ContainerViewController.swift 修改 updateNavigationColors() 方法为:

    func updateNavigationColors() {
    navigationController?.navigationBar.tintColor = RCValues.sharedInstance.color(forKey: .navTintColor)
    }
  2. 修改 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)
    }
  3. 修改 GetNewsletterViewController.swift 的 updateText()方法:

    func updateText() {
    instructionLabel.text = RCValues.sharedInstance.string(forKey: .subscribeVCText)
    submitButton.setTitle(RCValues.sharedInstance.string(forKey: .subscribeVCButton), for: .normal)
    }
  4. 将 PlanetDetailViewController.swift 的 updateLabelColors() 方法的这句:

    nextLabel.textColor = AppConstants.detailInfoColor
    

    修改为:

    nextLabel.textColor = RCValues.sharedInstance.color(forKey: .detailInfoColor)
    
  5. planetNameLabel.textColor = AppConstants.detailTitleColor
    

    修改为

    planetNameLabel.textColor = RCValues.sharedInstance.color(forKey: .detailTitleColor)
  6. 将 PlanetsCollectionViewController.swift 中 customizeNavigationBar() 方法的这句:

    navBar.barTintColor = AppConstants.navBarBackground
    

    修改为:

    navBar.barTintColor = RCValues.sharedInstance.color(forKey: .navBarBackground)
    
  7. 将 collectionView(_:cellForItemAt:) 的这句:

    cell.nameLabel.textColor = AppConstants.bigLabelColor
    

    修改为:

    cell.nameLabel.textColor = RCValues.sharedInstance.color(forKey: .bigLabelColor)

  8. 将 SolarSystem.swift 的 init() 的这句:

    if AppConstants.shouldWeIncludePluto {
    

    修改为:

    if RCValues.sharedInstance.bool(forKey: .shouldWeIncludePluto) {
    
  9. 将 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 配色方案),请在论坛中留言!

Download Materials

猜你喜欢

转载自blog.csdn.net/kmyhy/article/details/81205568
今日推荐