SwiftyRemoteConfig

Platforms CocoaPods compatible Carthage compatible SPM compatible Swift version Swift version Swift version

用于 FirebaseRemoteConfig 的现代 Swift API

SwiftyRemoteConfig 通过将富有表现力的 Swifty API 与静态类型的优势相结合,使 Firebase Remote Config 的使用变得愉快。这个库深受 SwiftyUserDefaults 的启发。

注意!需要在 Xcode 13.3 或更高版本中使用权变方法

由于 Xcode 编译器错误,您需要使用权变方法才能在 Xcode 13.3 或更高版本中使用此库。以下是推荐的权变方法步骤。

  1. 在使用 SwiftyRemoteConfig 的模块中创建 SwiftyRemoteConfig+Workaround.swift 文件。
  2. 将以下代码复制到 SwiftyRemoteConfig+Workaround.swift 中。 这几乎是 Sources 文件夹中 BuiltIns.swift 文件的副本:https://raw.githubusercontent.com/fumito-ito/SwiftyRemoteConfig/master/Sources/SwiftyRemoteConfig/BuiltIns.swift
import Foundation
import SwiftyRemoteConfig

extension RemoteConfigSerializable {
    public static var _remoteConfigArray: RemoteConfigArrayBridge<[T]> { RemoteConfigArrayBridge() }
}

extension Date: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigObjectBridge<Date> { RemoteConfigObjectBridge() }
}

extension String: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigStringBridge { RemoteConfigStringBridge() }
}

extension Int: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigIntBridge { RemoteConfigIntBridge() }
}

extension Double: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigDoubleBridge { return RemoteConfigDoubleBridge() }
}

extension Bool: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigBoolBridge { RemoteConfigBoolBridge() }
}

extension Data: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigDataBridge { RemoteConfigDataBridge() }
}

extension URL: RemoteConfigSerializable {
    public static var _remoteConfig: RemoteConfigUrlBridge { RemoteConfigUrlBridge() }
    public static var _remoteConfigArray: RemoteConfigCodableBridge<[URL]> { RemoteConfigCodableBridge() }
}

extension RemoteConfigSerializable where Self: Codable {
    public static var _remoteConfig: RemoteConfigCodableBridge<Self> { RemoteConfigCodableBridge() }
    public static var _remoteConfigArray: RemoteConfigCodableBridge<[Self]> { RemoteConfigCodableBridge() }
}

extension RemoteConfigSerializable where Self: RawRepresentable {
    public static var _remoteConfig: RemoteConfigRawRepresentableBridge<Self> { RemoteConfigRawRepresentableBridge() }
    public static var _remoteConfigArray: RemoteConfigRawRepresentableArrayBridge<[Self]> { RemoteConfigRawRepresentableArrayBridge() }
}

extension RemoteConfigSerializable where Self: NSCoding {
    public static var _remoteConfig: RemoteConfigKeyedArchiverBridge<Self> { RemoteConfigKeyedArchiverBridge() }
    public static var _remoteConfigArray: RemoteConfigKeyedArchiverArrayBridge<[Self]> { RemoteConfigKeyedArchiverArrayBridge() }
}

extension Dictionary: RemoteConfigSerializable where Key == String {
    public typealias T = [Key: Value]
    public typealias Bridge = RemoteConfigObjectBridge<T>
    public typealias ArrayBridge = RemoteConfigArrayBridge<[T]>

    public static var _remoteConfig: Bridge { Bridge() }
    public static var _remoteConfigArray: ArrayBridge { ArrayBridge() }
}

extension Array: RemoteConfigSerializable where Element: RemoteConfigSerializable {
    public typealias T = [Element.T]
    public typealias Bridge = Element.ArrayBridge
    public typealias ArrayBridge = RemoteConfigObjectBridge<[T]>

    public static var _remoteConfig: Bridge { Element._remoteConfigArray }
    public static var _remoteConfigArray: ArrayBridge {
        fatalError("Multidimensional arrays are not supported yet")
    }
}

extension Optional: RemoteConfigSerializable where Wrapped: RemoteConfigSerializable {
    public typealias Bridge = RemoteConfigOptionalBridge<Wrapped.Bridge>
    public typealias ArrayBridge = RemoteConfigOptionalBridge<Wrapped.ArrayBridge>

    public static var _remoteConfig: Bridge { RemoteConfigOptionalBridge(bridge: Wrapped._remoteConfig) }
    public static var _remoteConfigArray: ArrayBridge { RemoteConfigOptionalBridge(bridge: Wrapped._remoteConfigArray) }
}

特性

只需一步即可开始使用 SwiftyRemoteConfig。

定义您的键!

extension RemoteConfigKeys {
    var recommendedAppVersion: RemoteConfigKey<String?> { .init("recommendedAppVersion")}
    var isEnableExtendedFeature: RemoteConfigKey<Bool> { .init("isEnableExtendedFeature", defaultValue: false) }
}

...然后就可以使用了!

// get remote config value easily
let recommendedVersion = RemoteConfigs[.recommendedAppVersion]

// eality work with custom deserialized types
let themaColor: UIColor = RemoteConfigs[.themaColor]

如果您使用 Swift 5.1 或更高版本,您还可以使用 keyPath dynamicMemberLookup

let subColor: UIColor = RemoteConfigs.subColor

用法

定义您的键

要充分利用 SwiftyRemoteConfig,请提前定义您的远程配置键

let flag = RemoteConfigKey<Bool>("flag", defaultValue: false)

只需创建一个 RemoteConfigKey 对象。 如果您想要一个非可选的值,只需在键中提供一个 defaultValue(请看上面的例子)。

您现在可以使用 RemoteConfig 快捷方式来访问这些值

RemoteConfigs[key: flag] // => false, type as "Bool"

编译器不会让您获取便捷地返回 Bool

使用快捷方式

为了更加方便,请通过扩展 magic RemoteConfigKeys 类并添加静态属性来定义您的键

extension RemoteConfigKeys {
    var flag: RemoteConfigKey<Bool> { .init("flag", defaultValue: false) }
    var userSectionName: RemoteConfigKey<String?> { .init("default") }
}

并使用快捷点语法

RemoteConfigs[\.flag] // => false

支持的类型

SwiftyRemoteConfig 支持以下标准类型

单个值 数组
String(字符串) [String](字符串数组)
Int(整数) [Int](整数数组)
Double(双精度浮点数) [Double](双精度浮点数数组)
Bool(布尔值) [Bool](布尔值数组)
Data(数据) [Data](数据数组)
Date(日期) [Date](日期数组)
URL [URL](URL 数组)
[String: Any](字符串到 Any 类型的字典) [[String: Any]](字符串到 Any 类型字典的数组)

但这还不是全部!

扩展现有类型

Codable

SwiftyRemoteConfig 支持 Codable! 只需在您的类型中遵循 RemoteConfigSerializable

final class UserSection: Codable, RemoteConfigSerializable {
    let name: String
}

无需实现! 这样做您可以选择指定可选的 RemoteConfigKey

let userSection = RemoteConfigKey<UserSection?>("userSection")

此外,您还将免费获得数组支持

let userSections = RemoteConfigKey<[UserSection]?>("userSections")

NSCoding

像支持 Codable 一样支持您的自定义 NSCoding 类型

final class UserSection: NSObject, NSCoding, RemoteConfigSerializable {
    ...
}

RawRepresentable

最后,RawRepresentable 支持! 同样,情况与 CodableNSCoding 相同

enum UserSection: String, RemoteConfigSerializable {
    case Basic
    case Royal
}

自定义类型

如果您想添加我们尚未支持的自己的自定义类型,我们已经为您考虑到了。 我们使用多种 RemoteConfigBridge 来指定您如何获取值和值数组。 当您查看 RemoteConfigSerializable 协议时,它期望每种类型中都有两个属性:_remoteConfig_remoteConfigArray,两者都属于 RemoteConfigBridge 类型。

例如,这是使用 NSKeyedUnarchiver 进行单值数据检索的桥接

public struct RemoteConfigKeyedArchiveBridge<T>: RemoteConfigBridge {

    public func get(key: String, remoteConfig: RemoteConfig) -> T? {
        remoteConfig.data(forKey: key).flatMap(NSKeyedUnarchiver.unarchiveObject) as? T
    }

    public func deserialize(_ object: RemoteConfigValue) -> T? {
        guard let data = object as? Data else {
            return nil
        }

        NSKeyedUnarchiver.unarchiveObject(with: data)
    }
}

用于默认检索数组值的桥接

public struct RemoteConfigArrayBridge<T: Collection>: RemoteConfigBridge {
    public func get(key: String, remoteConfig: RemoteConfig) -> T? {
        remoteConfig.array(forKey: key) as? T
    }

    public func deserialize(_ object: RemoteConfigValue) -> T? {
        return nil
    }
}

现在,要在您的类型中使用这些桥接,您只需如下声明它

struct CustomSerializable: RemoteConfigSerializable {
    static var _remoteConfig: RemoteConfigBridge<CustomSerializable> { RemoteConfigKeyedArchiverBridge() }
    static var _remoteConfigArray: RemoteConfigBridge<[CustomSerializable]> { RemoteConfigKeyedArchiverBridge() }

    let key: String
}

不幸的是,如果您发现自己需要自定义桥接器,您可能需要自己编写一个

final class RemoteConfigCustomBridge: RemoteConfigBridge {
    func get(key: String, remoteConfig: RemoteConfig) -> RemoteConfigCustomSerializable? {
        let value = remoteConfig.string(forKey: key)
        return value.map(RemoteConfigCustomSerializable.init)
    }

    func deserialize(_ object: Any) -> RemoteConfigCustomSerializable? {
        guard let value = object as? String {
            return nil
        }

        return RemoteConfigCustomSerializable(value: value)
    }
}

final class RemoteConfigCustomArrayBridge: RemoteConfigBridge {
    func get(key: String, remoteConfig: RemoteConfig) -> [RemoteConfigCustomSerializable]? {
        remoteConfig.array(forKey: key)?
            .compactMap({ $0 as? String })
            .map(RemoteConfigCustomSerializable.init)
    }

    func deserialize(_ object: Any) -> [RemoteConfigCustomSerializable]? {
        guard let values as? [String] else {
            return nil
        }

        return values.map({ RemoteConfigCustomSerializable.init })
    }
}

struct RemoteConfigCustomSerializable: RemoteConfigSerializable, Equatable {
    static var _remoteConfig: RemoteConfigCustomBridge { RemoteConfigCustomBridge() }
    static var _remoteConfigArrray: RemoteConfigCustomArrayBridge: { RemoteConfigCustomArrayBridge() }

    let value: String
}

为了支持具有不同桥接器的现有类型,您可以类似地扩展它

extension Data: RemoteConfigSerializable {
    public static var _remoteConfigArray: RemoteConfigArrayBridge<[T]> { RemoteConfigArrayBridge() }
    public static var _remoteConfig: RemoteConfigBridge<T> { RemoteConfigBridge() }
}d

另外,请查看我们的源代码或测试,以查看更多桥接器的示例。 如果您发现自己对所有这些桥接器感到困惑,请创建一个 issue,我们将解决一些问题。

属性包装器

SwiftyRemoteConfig 为 Swift 5.1 提供了属性包装器! 属性包装器 @SwiftyRemoteConfig 提供了一个选项,可以使用 key path。

注意:此属性包装器仅支持 read。 您可以将新值设置为属性,但是任何更改都不会反映到远程配置值

用法

给定键

extension RemoteConfigKeys {
    var userColorScheme: RemoteConfigKey<String> { .init("userColorScheme", defaultValue: "default") }
}

您可以声明一个 Settings 结构

struct Settings {
    @SwiftyRemoteConfig(keyPath: \.userColorScheme)
    var userColorScheme: String
}

您还可以使用 projected value 检查属性详细信息

struct Settings {
    @SwiftyRemoteConfig(keyPath: \.newFeatureAvailable)
    var newFeatureAvailable: String
}

struct NewFeatureRouter {
    func show(with settings: Settings) {
        if settings.$newFeatureAvailable.lastFetchTime != nil {
            // show new feature
        } else {
            // fetch and activate remote config before routing
        }
    }
}

KeyPath dynamicMemberLookup

SwiftyRemoteConfig 使 KeyPath dynamicMemberLookup 可以在 Swift 5.1 中使用。

extension RemoteConfigKeys {
    var recommendedAppVersion: RemoteConfigKey<String?> { .init("recommendedAppVersion")}
    var themaColor: RemoteConfigKey<UIColor> { .init("themaColor", defaultValue: .white) }
}

然后就可以使用了 ;-)

// get remote config value easily
let recommendedVersion = RemoteConfig.recommendedAppVersion

// eality work with custom deserialized types
let themaColor: UIColor = RemoteConfig.themaColor

Combine

SwiftyRemoteConfig 通过 Combine 的流提供来自 RemoteConfig 的值。

extension RemoteConfigKeys {
    var contentText: RemoteConfigKey<String> { .init("content_text", defaultValue: "Hello, World!!") }
}

并从 Combine 流中获取 RemoteConfig 的值!

import FirebaseRemoteConfig
import SwiftyRemoteConfig
import Combine

final class ViewModel: ObservableObject {
    @Published var contentText: String

    private var cancellables: Set<AnyCancellable> = []

    init() {
        contentText = RemoteConfigs.contentText

        RemoteConfig.remoteConfig()
            .combine
            .fetchedPublisher(for: \.contentText)
            .receive(on: RunLoop.main)
            .assign(to: \.contentText, on: self)
            .store(in: &cancellables)
    }
}

依赖项

SDK

框架

安装

Cocoapods

如果您使用的是 Cocoapods,只需将此行添加到您的 Podfile

pod 'SwiftyRemoteConfig`, `~> 1.0.0`

通过在您的终端中运行此命令进行安装

$ pod install

然后在您使用它的所有文件中导入该库

import SwiftyRemoteConfig

Carthage

只需添加您的 Cartfile

github "fumito-ito/SwiftyRemoteConfig" ~> 1.0.0

Swift Package Manager

只需在您的 Package.swift 的 dependencies 下添加

let package = Package(
    name: "MyPackage",
    products: [...],
    dependencies: [
        .package(url: "https://github.com/fumito-ito/SwiftyRemoteConfig.git", .upToNextMajor(from: "1.0.0"))
    ]
)

SwiftyRemoteConfig 在 Apache License 2.0 下可用。 有关更多详细信息,请参见 LICENSE 文件。