YMFF 是一个精巧的库,借助 Swift 的 属性包装器 和(未来)宏,让使用功能标志管理功能特性以及管理功能标志本身变得非常轻松愉快。
我工作过的每家公司都需要一种方法来管理已发布到用户应用中的功能可用性。令人惊讶的是,功能标志(又名 功能开关 或 功能切换)往往会引起很多麻烦。
我渴望改变这种状况。
YMFF 开箱即用,完全准备就绪:您只需几行代码即可获得开始使用所需的一切。
我相信您知道如何安装依赖项。YMFF 同时支持 SPM 和 CocoaPods。
要将 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")
YMFF 支持通过 CocoaPods 安装,但请记住,此支持是在尽力而为的基础上提供的。
将以下内容添加到您的 Podfile
pod 'YMFF', '~> 4.0'
YMFF 依赖于功能标志存储的概念——功能标志值的 “真理来源”。
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
来读取和写入功能标志值。您的更改将在应用重启后保持不变。
import YMFF
private static let resolver = FeatureFlagResolver(stores: [UserDefaultsStore()])
就是这样!
您可以浏览源文件以了解更多可用选项。
UserDefaultsStore
中支持可选值此版本预计在 2024 年末,Swift 6 发布之后。
如果有什么问题无法正常工作—或者您有建议,请随时提出新的 issue。