JustTweak
JustTweak 是一个用于 iOS 应用的功能标志框架。它提供了一个简单的门面接口,与多个查询时尊重特定优先级的调整提供者进行交互。调整表示用于驱动客户端代码中决策的标志。
使用 JustTweak,您可以实现以下功能:
- 使用 JSON 文件提供功能标志的默认值
- 使用 Firebase 和 Optmizely 等多个远程调整提供者进行 A/B 测试和功能标志
- 在运行时本地启用、禁用和自定义功能
- 提供专门的 UI 进行自定义(这对正在开发中的功能特别有用,可以展示给利益相关者)
安装
CocoaPods
JustTweak 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中即可
pod "JustTweak"
Swift包管理器
JustTweak也可以通过SPM获取。复制此仓库的URL,并将包添加到项目设置中。
实现
集成
- 定义一个包含您的功能的
LocalTweakProvider
JSON文件。请参考LocalTweaks_example.json
作为起点。 - 配置堆栈
配置堆栈,您有两个选择:
- 手动实现堆栈
- 利用代码生成工具
手动集成
- 按照以下方式配置JustTweak堆栈:
static let tweakManager: TweakManager = {
var tweakProviders: [TweakProvider] = []
// Mutable TweakProvider (to override tweaks from other TweakProviders)
let userDefaultsTweakProvider = UserDefaultsTweakProvider(userDefaults: UserDefaults.standard)
tweakProviders.append(userDefaultsTweakProvider)
// Optimizely (remote TweakProvider)
let optimizelyTweakProvider = OptimizelyTweakProvider()
optimizelyTweakProvider.userId = UUID().uuidString
tweakProviders.append(optimizelyTweakProvider)
// Firebase Remote Config (remote TweakProvider)
let firebaseTweakProvider = FirebaseTweakProvider()
tweakProviders.append(firebaseTweakProvider)
// Local JSON-based TweakProvider (default TweakProvider)
let jsonFileURL = Bundle.main.url(forResource: "LocalTweaks_example", withExtension: "json")!
let localTweakProvider = LocalTweakProvider(jsonURL: jsonFileURL)
tweakProviders.append(localTweakProvider)
return TweakManager(tweakProviders: tweakProviders)
}()
- 实现由
LocalTweakProvider
支持的属性和常量。请参考TweakAccessor.swift
作为起点。
使用代码生成工具
- 按以下格式在
config.json
文件中定义堆栈配置
{
"accessorName": "GeneratedTweakAccessor"
}
目前支持的唯一值是accessorName
,它定义了生成的类的名称。
- 将以下内容添加到您的
Podfile
script_phase :name => 'TweakAccessorGenerator',
:script => '$SRCROOT/../TweakAccessorGenerator \
-l $SRCROOT/<path_to_the_local_tweaks_json_file> \
-o $SRCROOT/<path_to_the_output_folder_for_the_generated_code> \
-c $SRCROOT/<path_to_the_folder_containing_config.json>',
:execution_position => :before_compile
每次构建目标时,代码生成工具都会重新生成堆栈的代码。它将包含在LocalTweakProvider
中定义的所有 backing properties。
- 将生成的文件添加到您项目中并开始使用堆栈。
使用方法
基础
如果您已使用代码生成工具,生成的堆栈将包含所有特性标志。只需分配访问器对象(对象名称在.json
配置文件中已定义),然后使用它来访问特性标志。
let accessor = GeneratedTweakAccessor(with: <#tweak_manager_instance#>)
if accessor.meaningOfLife == 42 {
...
}
请参考GeneratedTweakAccessor.swift
和GeneratedTweakAccessor+Constants.swift
中的示例生成代码。
高级
如果您决定自己实现堆栈代码,您将需要通过《TweakManager
》实现访问特性的代码。
可以从《TweakManager
》实例访问JustTweak的三个主要特性来驱动代码路径决策。
- 检查特性是否启用
// check for a feature to be enabled
let enabled = tweakManager.isFeatureEnabled("some_feature")
if enabled {
// enable the feature
} else {
// default behaviour
}
- 获取给定特性的标志值。《
TweakManager
》将从具有最高优先级的特性提供者返回值,并在找不到设置值时自动回退到其他提供者。如果在特性中找不到nil Tweak,则抛出异常,该异常可以捕获并按需处理。
使用tweakWith(feature:variable:)
或提供的属性包装器。
// check for a tweak value
let tweak = try? tweakManager.tweakWith(feature: "some_feature", variable: "some_flag")
if let tweak = tweak {
// tweak was found in some tweak provider, use tweak.value
} else {
// tweak was not found in any tweak provider
}
或者用do-catch
// check for a tweak value
do {
let tweak = try tweakManager.tweakWith(feature: "some_feature", variable: "some_flag")
// tweak was found in some tweak provider, use tweak.value
return tweak
} catch let error as TweakError {
switch error {
case .notFound: () // "Feature or variable is not found"
case .notSupported: () // "Variable type is not supported"
case .decryptionClosureNotProvided: () // "Value is encrypted but there's no decryption closure provided"
}
} catch let error { // add a default catch to satisfy the compiler
print(error.localizedDescription)
}
提供@TweakProperty
、@OptionalTweakProperty
和@FallbackTweakProperty
属性包装器,用于标记表示特性标志的属性。请注意,为了使用这些属性包装器,需要一个TweakManager
的静态实例。
@TweakProperty(feature: <#feature_key#>,
variable: <#variable_key#>,
tweakManager: <#TweakManager#>)
var labelText: String
@OptionalTweakProperty(fallbackValue: <#nillable_fallback_value#>,
feature: <#feature_key#>,
variable: <#variable_key#>,
tweakManager: <#TweakManager#>)
var meaningOfLife: Int?
@FallbackTweakProperty(fallbackValue: <#nillable_fallback_value#>,
feature: <#feature_key#>,
variable: <#variable_key#>,
tweakManager: <#TweakManager#>)
var shouldShowFeatureX: Bool
特性提供者优先级
tweakProviders
数组中对象的顺序定义了特性提供者的优先级。
具有最高优先级的《MutableTweakProvider
》,如上面的示例中的《UserDefaultsTweakProvider
》,将用于反映UI(TweakViewController
)中做出的更改。应将《LocalTweakProvider
》设为最低优先级,因为它提供来自本地特性提供者的默认值,并且是《TweakViewController》用来填充UI的。
迁移说明
为了从手动方式迁移到代码生成的实现,需要更新到新的.json
格式。为了帮助这个过程,我们为tweak对象添加了GeneratedPropertyName
属性。将此值设置为与您的当前属性名相对应,以便生成的访问器属性与现有实现匹配。
缓存说明
为了提高性能,《TweakManager》提供了缓存tweak值的选项。默认情况下缓存是禁用的,但可以通过useCache
属性启用。启用后,有两种方式可以重置缓存:
- 在《TweakManager》对象上调用
resetCache
方法 - 发出
TweakProviderDidChangeNotification
通知
在运行时更新可变tweak提供者
JustTweak附带一个ViewController,允许用户以最高优先级编辑MutableTweakProvider
。
func presentTweakViewController() {
let tweakViewController = TweakViewController(style: .grouped, tweakManager: <#TweakManager#>)
// either present it modally
let tweaksNavigationController = UINavigationController(rootViewController:tweakViewController)
tweaksNavigationController.navigationBar.prefersLargeTitles = true
present(tweaksNavigationController, animated: true, completion: nil)
// or push it on an existing UINavigationController
navigationController?.pushViewController(tweakViewController, animated: true)
}
当任何MutableTweakProvider
中的值被修改时,将发出通知,以便客户端有机会做出反应并更新UI中的更改。
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.defaultCenter().addObserver(self,
selector: #selector(updateUI),
name: TweakProviderDidChangeNotification,
object: nil)
}
@objc func updateUI() {
// update the UI accordingly
}
自定义
JustTweak自带三个tweak提供者
UserDefaultsTweakProvider
,它可变,并使用UserDefaults
作为键值存储LocalTweakProvider
,它是只读的,并使用一个JSON文件,该文件旨在存储默认功能标志设置EphemeralTweakProvider
,它仅是NSMutableDictionary
的一个实例
此外,JustTweak定义了TweakProvider
和MutableTweakProvider
协议,您可以实现这些协议来创建自己的tweak提供者,以满足您的需求。在示例项目中可以找到一些示例,您可以用作起始点。
加密或预处理(高级)
JustTweak 允许在 TweakProvider
中添加一个 decryptionClosure
。这个闭包接收一个 Tweak
作为输入并返回一个 TweakValue
作为输出。这个闭包允许你在你的修改上进行一些预处理,例如用于解密值。如果您的 tweak JSON 文件中有加密值,可以这样做
"encrypted_answer_to_the_universe": {
"Title": "Encrypted definitive answer",
"Description": "Encrypted answer to the Ultimate Question of Life, the Universe, and Everything",
"Group": "General",
"Value": "24 ton yletinifeD",
"GeneratedPropertyName": "definitiveAnswerEncrypted",
"Encrypted": true
}
请注意,您必须在 JSON 文件中指定值是否加密(通过 Encrypted
属性),以便解密闭包处理该值。上述 JSON 的解密闭包可以这样指定
tweakProvider.decryptionClosure = { tweak in
// decrypt `tweak.value` with your cypher of choice and return the decrypted value
}
通过这种方式,从 tweak 提供器获取的 tweak 将包含解密的值。
授权
JustTweak 在 Apache 2.0 许可下可用。更多信息请参阅 LICENSE 文件。
- Just Eat iOS 团队