YMFF:轻松管理功能特性

YMFF 是一个精巧的库,借助 Swift 的 属性包装器 和(未来),让使用功能标志管理功能特性以及管理功能标志本身变得非常轻松愉快。

为什么 & 如何

我工作过的每家公司都需要一种方法来管理已发布到用户应用中的功能可用性。令人惊讶的是,功能标志(又名 功能开关功能切换)往往会引起很多麻烦。

我渴望改变这种状况。

YMFF 开箱即用,完全准备就绪:您只需几行代码即可获得开始使用所需的一切。

安装

我相信您知道如何安装依赖项。YMFF 同时支持 SPM 和 CocoaPods。

需要帮助?

Swift Package Manager (SPM)

要将 YMFF 添加到您的项目中,请使用 Xcode 内置的 Swift 包支持。点击 “File(文件)” → “Add Package Dependencies(添加包依赖项)”,并将以下 URL 粘贴到搜索字段中

https://github.com/yakovmanshin/YMFF

然后,系统会提示您选择要安装的版本并指示所需的更新策略。我建议从最新版本开始(它会自动选中),并选择 “Up to Next Major(更新到下一个主版本)” 作为更新规则。然后选择您要将 YMFF 链接到的目标。点击 “Finish(完成)” — 您就可以开始使用了!

如果您需要在另一个 Swift 包中使用 YMFF,请将其作为依赖项添加到 Package.swift 文件中

.package(url: "https://github.com/yakovmanshin/YMFF", from: "4.0.0")

CocoaPods

YMFF 支持通过 CocoaPods 安装,但请记住,此支持是在尽力而为的基础上提供的。

将以下内容添加到您的 Podfile

pod 'YMFF', '~> 4.0'

设置

YMFF 依赖于功能标志存储的概念——功能标志值的 “真理来源”。

Firebase Remote Config

Firebase Remote Config 是最流行的远程控制功能标志的工具之一。YMFF 与 Remote Config 无缝集成,尽管需要一些手动操作。

典型设置
import FirebaseRemoteConfig
import YMFFProtocols

extension RemoteConfig: SynchronousFeatureFlagStore {
    
    public func valueSync<Value>(for key: FeatureFlagKey) -> Result<Value, FeatureFlagStoreError> {
        // Remote Config returns a default value if the requested key doesn’t exist,
        // so you need to check the key for existence explicitly.
        guard allKeys(from: .remote).contains(key) else {
            return .failure(.valueNotFound)
        }
        
        let remoteConfigValue = self[key]
        
        let value: Value?
        // You need to use different `RemoteConfigValue` methods, depending on the return type.
        // I know, it doesn’t look fancy.
        switch Value.self {
        case is Bool.Type:
            value = remoteConfigValue.boolValue as? Value
        case is Data.Type:
            value = remoteConfigValue.dataValue as? Value
        case is Double.Type:
            value = remoteConfigValue.numberValue.doubleValue as? Value
        case is Int.Type:
            value = remoteConfigValue.numberValue.intValue as? Value
        case is String.Type:
            value = remoteConfigValue.stringValue as? Value
        default:
            value = nil
        }
        
        if let value {
            return .success(value)
        } else {
            return .failure(.typeMismatch)
        }
    }
    
}

RemoteConfig 现在是一个有效的功能标志存储

或者,您可以创建一个自定义的包装器对象。这就是我在我的项目中为了避免紧耦合而做的事情。

用法

声明功能标志

以下是如何使用 YMFF 声明功能标志

import YMFF

// For convenience, organize feature flags in a separate namespace using an enum.
enum FeatureFlags {
    
    // `resolver` references one or more feature-flag stores.
    private static let resolver = FeatureFlagResolver(stores: [MyFeatureFlagStore.shared])
    
    // Feature flags are initialized with three pieces of data:
    // a key string, the default (fallback) value, and the resolver.
    @FeatureFlag("ads_enabled", default: false, resolver: Self.resolver)
    static var adsEnabled
    
    // Feature flags aren’t limited to booleans. You can use any type of value!
    @FeatureFlag("number_of_banners", default: 3, resolver: Self.resolver)
    static var numberOfBanners
    
    // Advanced: Sometimes you want to map raw values from the store
    // to native values used in your app. `MyFeatureFlagStore` below
    // stores values as strings, while the app uses an enum.
    // To switch between them, you use a `FeatureFlagValueTransformer`.
    @FeatureFlag(
        "ad_unit_kind",
        transformer: FeatureFlagValueTransformer { rawValue in
            AdUnitKind(rawValue: rawValue)
        } rawValueFromValue: { value in
            value.rawValue
        },
        default: .image,
        resolver: Self.resolver
    )
    static var adUnitKind
    
}

// You can use custom types for feature-flag values.
enum AdUnitKind: String {
    case text
    case image
    case video
}

读取值

对于使用功能标志的代码来说,该标志的行为就像其值的类型一样

if FeatureFlags.adsEnabled {
    switch FeatureFlags.adUnitKind {
    case .text:
        displayAdText()
    case .image:
        displayAdBanners(count: FeatureFlags.numberOfBanners)
    case .video:
        playAdVideo()
    }
}

写入值

YMFF 允许您将功能标志值写入可变存储。就像为标志分配一个新值一样简单

FeatureFlags.adsEnabled = true

要删除该值,您需要在 FeatureFlag投影值(即 FeatureFlag 实例本身,而不是其包装值)上调用 removeValueFromMutableStores()

// Here `FeatureFlags.$adsEnabled` has the type `FeatureFlag<Bool>`, 
// while `FeatureFlags.adsEnabled` is of type `Bool`.
FeatureFlags.$adsEnabled.removeValueFromMutableStore()

UserDefaults

您可以使用 UserDefaults 来读取和写入功能标志值。您的更改将在应用重启后保持不变。

import YMFF

private static let resolver = FeatureFlagResolver(stores: [UserDefaultsStore()])

就是这样!

更多

您可以浏览源文件以了解更多可用选项。

未来展望

下一版本路线图

此版本预计在 2024 年末,Swift 6 发布之后。

想法 & Bug 报告

如果有什么问题无法正常工作—或者您有建议,请随时提出新的 issue。