JustTweak 10.1.1

JustTweak 10.1.1

测试已测试
Lang语言 SwiftSwift
许可证 Apache-2.0
发布最后发布2022 年 12 月
SPM支持 SPM

Alberto De BortoliDimi ChakarovMark CousinsJust Eat Takeaway.com iOS 团队 维护。



JustTweak 10.1.1

  • Just Eat Takeaway iOS 团队

JustTweak Banner

JustTweak

Build Status Version License Platform

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.swiftGeneratedTweakAccessor+Constants.swift中的示例生成代码。

高级

如果您决定自己实现堆栈代码,您将需要通过《TweakManager》实现访问特性的代码。

可以从《TweakManager》实例访问JustTweak的三个主要特性来驱动代码路径决策。

  1. 检查特性是否启用
// check for a feature to be enabled
let enabled = tweakManager.isFeatureEnabled("some_feature")
if enabled {
    // enable the feature
} else {
    // default behaviour
}
  1. 获取给定特性的标志值。《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定义了TweakProviderMutableTweakProvider协议,您可以实现这些协议来创建自己的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 团队