FeatureFlags 3.0.0

FeatureFlags 3.0.0

Ross Butler 维护。




  • 作者
  • Ross Butler

FeatureFlags

Build Status Version Carthage compatible Maintainability License Reviewed by Hound

FeatureFlags 通过一个可能是与您的应用程序捆绑在一起的 JSON 文件,使您能够轻松地配置特性标志、A/B 和 MVT 测试。对于远程托管的配置文件,您可以在不发布新版本到 App Store 的情况下启用/禁用功能,更新 A/B 测试组中用户的比例,或者一旦决定功能已准备好发布,甚至可以将之前处于 A/B 测试中的功能推出给所有用户。

要了解更多关于如何使用 FeatureFlags 的信息,请查阅 主要演讲,查看 博客文章,或使用下方的目录

特性

  • 特性标志
  • A/B 测试和 MVT 测试
  • 特性 A/B 测试(与不包含此功能的对照组相比,启用功能)
  • 远程托管特性标志 JSON 配置,允许您在不发布新版本应用程序的情况下启用/禁用功能
  • 使用现有的 JSON 文件或托管全新的配置
  • 远程调整每个测试组中用户的比例
  • 一旦决定功能测试是否成功,将 A/B 测试转换为特性标志,即将功能推出给所有用户
  • 使用 FeatureFlagsViewController 在应用程序的调试构建中可视化标志和测试的状态

FeatureFlags 3.0.0 新特性是什么?

参阅 CHANGELOG.md

安装

Cocoapods

CocoaPods 是一个依赖管理器,可以将依赖项集成到您的 Xcode 工作空间中。要使用 RubyGems 安装它,请运行

gem install cocoapods

要使用 Cocoapods 安装 FeatureFlags,只需将以下行添加到您的 Podfile 中

pod "FeatureFlags"

然后运行以下命令

pod install

有关更多信息,请参阅 此处

Carthage

Carthage 是一个依赖管理器,它为手动的项目集成生成二进制文件。它可以通过使用命令 Homebrew 安装

brew update
brew install carthage

要将 FeatureFlags 集成到您的项目中,请在您的项目的 Cartfile 中添加以下行

github "rwbutler/FeatureFlags"

从 macOS 终端运行 carthage update --platform iOS 以构建框架,然后拖动 FeatureFlags.framework 到您的 Xcode 项目中。

有关更多信息,请参阅 此处

Swift 包管理器

Swift 包管理器(Swift Package Manager)是一个针对 Swift 模块的依赖管理器,自 Swift 3.0 开始被包含在构建系统中。它用于自动化依赖的下载、编译和链接。

要将 FeatureFlags 作为依赖项添加到 Swift 包中,按照如下方式在 Package.swift 文件中的 dependencies 条目中添加该包:

dependencies: [
    .package(url: "https://github.com/rwbutler/FeatureFlags.git", from: "2.0.0")
]

使用方法

将框架集成到项目中后,下一步是使用 JSON 文件进行配置,该文件可以是与应用程序捆绑的一部分,也可以放在远程服务器上。该 JSON 文件可以是新创建的,也可以是您已经使用的现有配置 JSON 文件。只需在文件的顶层添加一个名为 features 的键,映射到以下数组

{
    "features": []
}

数组的内等内容取决于要配置的功能标志和测试。

要让 FeatureFlags 知道您的配置文件的位置

guard let featuresURL = Bundle.main.url(forResource: "features", withExtension: "json") else { return }
FeatureFlags.configurationURL = featuresURL

或者

guard let featuresURL = URL(string: "https://www.exampledomain.com/features.json") else { return }
FeatureFlags.configurationURL = featuresURL

如果您选择在远程托管您的 JSON 文件,则可以将捆绑的回退作为应用程序包的一部分提供

guard let fallbackURL = Bundle.main.url(forResource: "features", withExtension: "json") else { return }
FeatureFlags.localFallbackConfigurationURL = fallbackURL

远程-hosted 的 JSON 文件将始终优于捆绑设置,并且远程定义的设置将被缓存在本地,这样,如果用户处于离线状态,将应用从网络获取的最后一个设置。

功能标志

为了配置功能标志,请在您的 JSON 配置中的功能数组中添加一个功能对象。

{
    "features": [{
        "name": "Example Feature Flag",
        "enabled": false
    }]
}

然后为 Feature.Name 添加一个扩展,在代码中如下导入功能标志:

import FeatureFlags

extension Feature.Name {
	static let exampleFeatureFlag = Feature.Name(rawValue: "Example Feature Flag")
}

确保原始值与 JSON 文件中的字符串匹配。然后调用以下内容检查功能标志是否启用

Feature.isEnabled(.exampleFeatureFlag)) 

如果你的Feature.Name扩展中指定的字符串与JSON文件中的值不匹配,则返回的默认值是false。如果你需要检查功能是否存在,你可以编写以下代码:

if let feature = Feature.named(.exampleFeatureFlag) {
	print("Feature name -> \(feature.name)")
	print("Feature enabled -> \(feature.isEnabled())")
}

A/B测试

要配置A/B测试,请将以下功能对象添加到你的JSON文件中的features数组中:

{
	"name": "Example A/B Test",
	"enabled": true,
	"test-variations": ["Group A", "Group B"]
}
  • enabled指示A/B测试是否启用。

功能标志和A/B测试之间的唯一区别在于为测试变体添加一个数组。FeatureFlags假设你在数组中添加两个测试变体时正在配置一个A/B测试——如果添加更多,测试将自动成为多变量测试(MVT)。

使用扩展Feature.Name将功能导入代码。

extension Feature.Name {
	static let exampleABTest = Feature.Name(rawValue: "Example A/B Test")
}

然后使用以下代码检查用户被分配到了哪个组:

if let test = ABTest(rawValue: .exampleABTest) {
	print("Is in group A? -> \(test.isGroupA())")
	print("Is in group B? -> \(test.isGroupB())")
}

或者,你可能更喜欢以下语法:

if let feature = Feature.named(.exampleABTest) {
	print("Feature name -> \(feature.name)")
	print("Is group A? -> \(feature.isTestVariation(.groupA))")
	print("Is group B? -> \(feature.isTestVariation(.groupB))")
	print("Test variation -> \(feature.testVariation())")
}

功能A/B测试

功能A/B测试是A/B测试的一个细微变化(及其子类型)。在通用的A/B测试中,你可能会想检查用户是否被放置在蓝色背景或红色背景的测试变体。功能A/B测试专门测试是否引入新功能相比没有该功能的控制组是一个改进。因此,在功能A/B测试中——功能要么开启要么关闭。

要配置功能A/B测试,请使用以下JSON:

{
	"name": "Example Feature A/B Test",
	"enabled": true,
	"test-variations": ["Enabled", "Disabled"]
}
  • enabled指示A/B测试是否启用。
extension Feature.Name {
	static let exampleFeatureABTest = Feature.Name(rawValue: "Example Feature A/B Test")
}

通过将测试变体命名为EnabledDisabled,FeatureFlags知道你的目的是设置一个功能A/B测试。

与通用A/B测试相比,配置功能A/B测试的优点在于,你不需要编写以下代码:

if let feature = Feature.named(.exampleFeatureABTest) {
	print("Feature name -> \(feature.name)")
	print("Is group A? -> \(feature.isTestVariation(.enabled))")
	print("Is group B? -> \(feature.isTestVariation(.disabled))")
	print("Test variation -> \(feature.testVariation())")
}

你可以简单地使用以下代码来确定用户被分配到哪个测试组:

Feature.isEnabled(.exampleFeatureABTest))

通常使用Feature.enabled()方法测试是否在全局范围内启用功能;在这个特定情况下,如果用户属于接收新功能的组,它将返回true;如果用户属于控制组,它将返回false。请注意,此方法还将返回false,如果JSON中该功能的enabled属性设置为false,即测试已全局禁用。

多元(MVT)测试

多元测试的配置与A/B测试的配置模式类似。请将以下特性对象添加到您的JSON文件中的特性数组中

{
	"name": "Example MVT Test",
	"enabled": true,
	"test-variations": ["Group A", "Group B", "Group C"]
}
  • enabled表示MVT测试是否已启用。

FeatureFlags知道您正在配置MVT测试,如果您向数组中添加了两个以上的测试变体。请就像为Feature.Name添加扩展一样将特性导入到代码中

extension Feature.Name {
	static let exampleMVTTest = Feature.Name(rawValue: "Example MVT Test")
}

使用以下代码检查用户被分配到哪个组

if let feature = Feature.named(.exampleMVTTest) {
	print("Feature name -> \(feature.name)")
	print("Is group A? -> \(feature.isTestVariation(.groupA))")
	print("Is group B? -> \(feature.isTestVariation(.groupB))”)
	print("Is group C? -> \(feature.isTestVariation(.groupC))”)
	print("Test variation -> \(feature.testVariation())”)
}

您可以为您的测试变体命名任意名称

{
	"name": "Example MVT Test",
	"enabled": true,
	"test-variations": ["Red", "Green", "Blue"]
}
  • enabled表示MVT测试是否已启用。

仅需在代码中为Test.Variation扩展以映射您的测试变体

extension Test.Variation {
	static let red = Test.Variation(rawValue: "Red")
	static let green = Test.Variation(rawValue: "Green")
	static let blue = Test.Variation(rawValue: "Blue")
}

然后检查用户被分配到哪个组

if let feature = Feature.named(.exampleMVTTest) {
	print("Feature name -> \(feature.name)")
	print("Is red? -> \(feature.isTestVariation(.red))")
	print("Is green? -> \(feature.isTestVariation(.green))")
	print("Is blue? -> \(feature.isTestVariation(.blue))")
	print("Test variation -> \(feature.testVariation())")
}

开发标志

当开发者提到特性标志时,他们通常指的是以下两种情况之一

  • 远程标志:允许我们远程切换已完成的特性,并将其推出给特定用户组。
  • 开发标志:允许我们隐藏开发中的特性,以保持代码的可发布性。

使用开发标志,我们永远不希望正在开发中的代码发布给用户。

假设我们想要使用远程特性标志来关闭一个未完成的特性,从而允许我们不包含该特性发布应用程序的版本1。如果我们稍后作为应用程序版本2的一部分完成该特性并切换该特性,则版本1的用户将体验到一个功能不完整的功能,因为正在开发中的版本1代码被启用。这是我们永远不会遇到的情况,因此FeatureFlags同时提供了远程标志和开发标志。

要将特性标志标记为开发标志,首先将包含features关键字的捆绑JSON文件作为应用程序的一部分。该JSON文件可以是现有文件或全新文件。接下来,在文件中定义您的特性标志后,将配置URL设置为其引用此文件的配置

guard let featuresURL = Bundle.main.url(forResource: "features", withExtension: "json") else { return }
FeatureFlags.configurationURL = featuresURL

或者,如果您已在使用远程配置URL,则设置备用配置URL

guard let fallbackURL = Bundle.main.url(forResource: "features", withExtension: "json") else { return }
FeatureFlags.localFallbackConfigurationURL = fallbackURL

以通常的方式设置JSON中的特性标志,但将development属性设置为true

{
    "features": [{
        "name": "Example Feature Flag",
        "development": true,
        "enabled": true
    }]
}

到这里就完成了!从现在起,这个特性标志将被视为开发标志,即使将其设置为enabled,其背后的代码也决不会被发布给用户。

在以下情况下将显示开发标志代码

  • 如果设置了#DEBUG预处理标志,并且标志处于启用状态。
  • 如果FeatureFlags.isDevelopment设置为true(当应用程序处于开发状态时,由开发人员决定将其设置为true - 默认值为false)并且标志处于启用状态。

如果您需要对处于开发状态下的代码进行更细粒度的控制,则可以在检查功能是否启用时传递isDevelopment标志,例如。

if let feature = Feature.named(.exampleFeatureFlag, isDevelopment: true) {
	print("Feature name -> \(feature.name)")
	print("Feature enabled -> \(feature.isEnabled())")
}

在本例中,只有当exampleFeatureFlag处于启用状态且应用程序处于开发状态时(即#DEBUGFeatureFlags.isDevelopment已设置),才会执行print语句。

解锁标志

无论功能标志是在本地还是远程控制,上述标志在全局级别操作,即它们将启用或禁用所有用户的功能。但是,如果我们想在用户完成内购后或作为达成某些目标的奖励来解锁功能,我们就需要一种方法来解锁功能并保持其解锁状态——我们通过解锁标志来实现这一点。

在您的配置JSON文件中配置解锁标志,请添加以下内容

{
    "features": [{
        "name": "Example Unlock Flag",
        "enabled": true,
        "unlocked": false
    }]
}

unlocked属性指示FeatureFlags这是一个解锁标志,其默认值为false,即功能最初将被锁定。请注意,如果enabled属性设置为false,则无论特定用户的功能是否已解锁,该功能将所有用户禁用,因为这属性在全局级别操作。

可以按以下方式查询解锁标志是否已解锁

if let feature = Feature.named(.exampleUnlockFlag) {
    print("Is unlocked? -> \(feature.isUnlocked())")
}

当您希望为用户解锁功能时,调用feature.unlock()。相反,如果您只想暂时解锁功能,例如允许用户尝试功能,稍后可以调用feature.lock()来再次使功能不可用。这两种方法都返回一个Bool,以指示功能是否已解锁/锁定。

if let feature = Feature.named(.exampleUnlockFlag) {
    print("Is unlocked? -> \(feature.unlock())")
}

注意,功能可能无法解锁以下情况

  • 功能标志不是解锁标志,即没有在JSON中定义unlocked属性。
  • enabled属性设置为false

高级用法

测试偏差

默认情况下,对于任何A/B或MVT测试,用户被分配到每个指定测试变体的可能性是相等的,即对于A/B测试,被分配到任意一组的概率均为50%/50%。对于具有四个变体的MVT测试,分配给每个变体的概率为25%。

可以配置测试偏差,使用户被分配到每个测试变体的可能性不平等。为此,只需将以下JSON添加到您的功能对象中

{
	"features": [{
		"name": "Example A/B Test",
		"enabled": true,
		"test-biases": [80, 20],
		"test-variations": ["Group A", "Group B"]
	}]
}

test-biases数组中指定的加权因子数量必须等于测试变体的数量,且总数必须等于100,否则将忽略加权因子,默认为等权。

标签

如果希望将有关测试组分配给用户的分析发送出去,可以给测试变体附加标签。

为此,定义一个与指定测试变体数量相同的labels数组。

{
	"features": [{
		"name": "Example A/B Test",
		"enabled": true,
		"test-biases": [50, 50],
		"test-variations": ["Group A", "Group B"],
		"labels": ["label1-for-analytics", "label2-for-analytics"]
	}]
}

然后在代码中检索您的标签时,写入以下内容

if let feature = Feature.named(.exampleABTest) {
	print("Group A label -> \(feature.label(.groupA))")
	print("Group B label -> \(feature.label(.groupB))")
}

部署功能

特征标志框架最强大的功能是能够在远程JSON配置文件中调整测试偏差,并自动将用户重新分配到新的测试组。例如,您可能决定在第一周使用10%/90%(10%的用户将接收新功能)的划分来部署功能,第二周为20%/80%,依此类推。

只需更新test-biases数组中的加权因子,下次框架检查您的JSON配置时,组将被重新分配。

当你完成A/B测试或MVT测试一个功能后,你将收集到足够的分析数据以决定是否将该功能推广到所有用户。在这个时候,你可以决定通过将JSON文件中的enabled标志设置为false来完全禁用该功能,或者在一个成功的测试中,你可以决定通过调整JSON文件中的功能对象来将该功能推广到所有用户。

{
	"features": [{
		"name": "Example A/B Test",
		"enabled": true,
		"test-biases": [50, 50],
		"test-variations": ["Group A", "Group B"],
		"labels": ["label1-for-analytics", "label2-for-analytics"]
	}]
}

以下是将功能对象从

{
	"features": [{
		"name": "Example A/B Test",
		"enabled": true
	}]
}

转换为针对所有用户全局启用的功能标志的示例

为了显示视图控制器,指定所需的导航偏好,然后通过提供UINavigationController来实现视图控制器。

 let navigationSettings = FeatureFlagsViewControllerNavigationSettings(autoClose: true, closeButtonAlignment: .right, closeButton: .save, isNavigationBarHidden: false)
 
FeatureFlags.pushFeatureFlags(delegate: self, navigationController: navigationController, navigationSettings: navigationSettings)

FeatureFlagsViewController

如果您需要有关每个功能标志/测试状态的更多信息,您可以使用3D Touch来预览/弹出更多信息。

FeatureDetailsViewController

如果您需要在任何时间刷新配置,可以调用FeatureFlags.refresh(),该方法可选接受一个完成闭包以在刷新完成后通知您。

如果您选择在应用程序已经获取的现有JSON文件中包含功能标志信息,您可能希望使用以下方法,将JSON文件数据传递以提高网络调用效率。

FeatureFlags.refreshWithData(_:completion:) 

虽然FeatureFlags主要用于支持Swift应用程序,但在需要时,即使是在Objective-C中,也可以检查功能标志是否启用,如下所示

static NSString *const kMyNewFeatureFlag = @"My New Feature Flag";

if (FEATURE_IS_ENABLED(kMyNewFeatureFlag)) {
    ...
}

作者

Ross Butler

许可证

FeatureFlags 软件在MIT许可证下可用。更多信息请参阅LICENSE文件

附加软件

控件

AnimatedGradientView
AnimatedGradientView

框架

  • Cheats - 为现代iOS应用提供复古作弊码。
  • Connectivity - 改进了Reachability,用于在iOS应用程序中确定Internet连接。
  • FeatureFlags - 允许开发者配置功能标志,使用打包的/远程托管的JSON配置文件运行多个A/B或MVT测试。
  • FlexibleRowHeightGridLayout - 一款设计用于支持动态字体的UICollectionView网格布局,允许每行的高度根据内容大小进行调整。
  • Hash - 使用流行的哈希函数(包括MD5、SHA-1、SHA-256)生成消息摘要和HMACs的轻量级方法。
  • Skylark - 完全使用Swift编写的BDD测试框架,用于使用Gherkin语法编写Cucumber场景。
  • TailorSwift - 列出有用的Swift Core Library / Foundation框架扩展。
  • TypographyKit - با پوزش Dynamic Type،的表现风格一致并易于访问的iOS UI。
  • Updates - 自动检测应用程序更新,并轻柔地提示用户进行更新。
技巧 连接性 特性标志 Skylark TypographyKit Updates
Cheats Connectivity FeatureFlags Skylark TypographyKit Updates

工具

  • Clear DerivedData - 通过在终端中键入 cdd 快速清除您的DerivedData目录的小工具。
  • Config Validator - 作为您的CI过程的一部分,验证并上传您的配置文件和清除CDN的配置验证器。
  • IPA Uploader - 将您的应用程序上传到TestFlight和App Store。
  • 调色板 - 使您的 TypographyKit 颜色调色板在Xcode界面构建器中可用。
配置验证器 IPA上传器 调色板
Config Validator IPA Uploader Palette