From 1a3382fa64d6a2728e584904fba560635ae2cf1f Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Sun, 30 May 2021 19:32:48 +0300 Subject: [PATCH 1/4] Updated FeatureFlagResolver.init(stores:) * The configuration object used in the initializer now has the type `FeatureFlagResolverConfigurationProtocol` set explicitly, in order to use the non-deprecated initializer --- Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift b/Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift index b80fb8e..3196a01 100644 --- a/Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift +++ b/Sources/YMFF/FeatureFlagResolver/FeatureFlagResolver.swift @@ -39,7 +39,8 @@ final public class FeatureFlagResolver { /// /// - Parameter stores: *Required.* The array of feature flag stores. public convenience init(stores: [FeatureFlagStore]) { - self.init(configuration: FeatureFlagResolverConfiguration(stores: stores)) + let configuration: FeatureFlagResolverConfigurationProtocol = FeatureFlagResolverConfiguration(stores: stores) + self.init(configuration: configuration) } deinit { From 97533cbb51373ed6f6745e4a6d9948f715c3a072 Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Sun, 30 May 2021 19:46:27 +0300 Subject: [PATCH 2/4] Updated README * Improved placement of code comments * Removed mentions of old versions * Various updates and improvements --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 03e48b2..fc87442 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # YMFF: Feature management made easy -Every company I worked at needed a way to manage availability of features in the apps already shipped to customers. Surprisingly enough, [feature flags](https://en.wikipedia.org/wiki/Feature_toggle) (a.k.a. feature toggles a.k.a. feature switches) tend to cause a lot of struggle. +Every company I worked for needed a way to manage availability of features in the apps already shipped to users. Surprisingly enough, [feature flags](https://en.wikipedia.org/wiki/Feature_toggle) (a.k.a. feature toggles a.k.a. feature switches) tend to cause a lot of struggle. I aspire to change that. -YMFF is a nice little library that makes management of features with feature flags—and management of the feature flags themselves—a bliss, thanks to Swift’s [property wrappers](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617). +YMFF is a nice little library that makes management of features with feature flags—and management of the feature flags themselves—a bliss, thanks to the power of Swift’s [property wrappers](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617). -YMFF ships completely ready for use, right out of the box: you get everything you need to start in just a few minutes. But you can also replace nearly any component of the system with your own, customized implementation. Since version 2.0, the implementation and the protocols are in two separate targets (YMFF and YMFFProtocols, respectively). +YMFF ships completely ready for use, right out of the box: you get everything you need to start in just a few minutes. But you can also replace nearly any component of the system with your own, customized implementation. The supplied implementation and the protocols are kept in two separate targets (YMFF and YMFFProtocols, respectively). ## Installation @@ -82,22 +82,22 @@ extension RemoteConfig: FeatureFlagStoreProtocol { Now `RemoteConfig` is a valid feature flag store. -Alternatively, instead of extending `RemoteConfig`, you can create a custom wrapper object. That’s what I prefer to do in my projects. +Alternatively, you can create a custom wrapper object instead of extending `RemoteConfig`. That’s what I prefer to do in my projects for better flexibility. ## Usage -Here’s the most basic way to use YMFF. +Here’s the most basic way to use YMFF: ```swift import YMFF -// For convenience, use an enum to create a namespace. +// For convenience, organize feature flags in a separate namespace using an enum. enum FeatureFlags { // `resolver` references one or more feature flag stores. - // `MyFeatureFlagStore.shared` conforms to `FeatureFlagStoreProtocol`. private static var resolver = FeatureFlagResolver(configuration: .init(stores: [ // If you want to change feature flag values from within your app, you’ll need at least one mutable store. .mutable(RuntimeOverridesStore()), + // `MyFeatureFlagStore.shared` conforms to `FeatureFlagStoreProtocol`. .immutable(MyFeatureFlagStore.shared), ])) @@ -142,7 +142,7 @@ FeatureFlags.$promoEnabled.removeValueFromMutableStore() ### `UserDefaults` -Since v1.2.0, you can use `UserDefaults` to read and write feature flag values. Your changes will persist when the app is restarted. +You can use `UserDefaults` to read and write feature flag values. Your changes will persist when the app is restarted. ```swift import YMFF From 320576764b821168a6856319702b58e47e459e26 Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Sat, 5 Jun 2021 13:28:59 +0300 Subject: [PATCH 3/4] [#74] Refactored Tests (#77) --- .../YMFFTests/SharedAssets/SharedAssets.swift | 12 ++-- .../Stores/MutableFeatureFlagStore.swift | 58 +++++++++++++++++++ .../{ => Stores}/MutableStoreTests.swift | 51 +--------------- .../RuntimeOverridesStoreTests.swift | 0 .../{ => Stores}/UserDefaultsStoreTests.swift | 0 5 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 Tests/YMFFTests/Stores/MutableFeatureFlagStore.swift rename Tests/YMFFTests/{ => Stores}/MutableStoreTests.swift (64%) rename Tests/YMFFTests/{ => Stores}/RuntimeOverridesStoreTests.swift (100%) rename Tests/YMFFTests/{ => Stores}/UserDefaultsStoreTests.swift (100%) diff --git a/Tests/YMFFTests/SharedAssets/SharedAssets.swift b/Tests/YMFFTests/SharedAssets/SharedAssets.swift index 27b9c5f..b0b435a 100644 --- a/Tests/YMFFTests/SharedAssets/SharedAssets.swift +++ b/Tests/YMFFTests/SharedAssets/SharedAssets.swift @@ -15,23 +15,23 @@ import YMFFProtocols enum SharedAssets { - static var configuration: FeatureFlagResolverConfiguration { - .init(stores: [ + static var configuration: FeatureFlagResolverConfigurationProtocol { + FeatureFlagResolverConfiguration(stores: [ .mutable(RuntimeOverridesStore()), .immutable(OpaqueStoreWithLimitedTypeSupport(store: remoteStore)), .immutable(localStore), ]) } - static var configurationWithNoMutableStores: FeatureFlagResolverConfiguration { - .init(stores: [ + static var configurationWithNoMutableStores: FeatureFlagResolverConfigurationProtocol { + FeatureFlagResolverConfiguration(stores: [ .immutable(OpaqueStoreWithLimitedTypeSupport(store: remoteStore)), .immutable(localStore), ]) } - static var configurationWithNoStores: FeatureFlagResolverConfiguration { - .init(stores: []) + static var configurationWithNoStores: FeatureFlagResolverConfigurationProtocol { + FeatureFlagResolverConfiguration(stores: []) } private static var localStore: [String : Any] { [ diff --git a/Tests/YMFFTests/Stores/MutableFeatureFlagStore.swift b/Tests/YMFFTests/Stores/MutableFeatureFlagStore.swift new file mode 100644 index 0000000..287e7f5 --- /dev/null +++ b/Tests/YMFFTests/Stores/MutableFeatureFlagStore.swift @@ -0,0 +1,58 @@ +// +// MutableFeatureFlagStore.swift +// YMFF +// +// Created by Yakov Manshin on 5/30/21. +// Copyright © 2021 Yakov Manshin. See the LICENSE file for license info. +// + +import YMFF +#if !COCOAPODS +import YMFFProtocols +#endif + +final class MutableFeatureFlagStore: MutableFeatureFlagStoreProtocol { + + private var store: TransparentFeatureFlagStore + private var onSaveChangesClosure: () -> Void + + init(store: TransparentFeatureFlagStore, onSaveChanges: @escaping () -> Void = { }) { + self.store = store + self.onSaveChangesClosure = onSaveChanges + } + + func containsValue(forKey key: String) -> Bool { + store[key] != nil + } + + func value(forKey key: String) -> Value? { + let expectedValueType = Value.self + + switch expectedValueType { + case is Bool.Type, + is Int.Type, + is String.Type, + is Optional.Type, + is Optional.Type, + is Optional.Type: + return store[key] as? Value + default: + assertionFailure("The expected feature flag value type (\(expectedValueType)) is not supported") + return nil + } + } + + func setValue(_ value: Value, forKey key: String) { + store[key] = value + } + + func removeValue(forKey key: String) { + store.removeValue(forKey: key) + } + + func saveChanges() { + onSaveChangesClosure() + } + +} + diff --git a/Tests/YMFFTests/MutableStoreTests.swift b/Tests/YMFFTests/Stores/MutableStoreTests.swift similarity index 64% rename from Tests/YMFFTests/MutableStoreTests.swift rename to Tests/YMFFTests/Stores/MutableStoreTests.swift index d00c782..07c816c 100644 --- a/Tests/YMFFTests/MutableStoreTests.swift +++ b/Tests/YMFFTests/Stores/MutableStoreTests.swift @@ -13,8 +13,6 @@ import YMFFProtocols @testable import YMFF -// MARK: - MutableStoreTests - final class MutableStoreTests: XCTestCase { private var mutableStore: MutableFeatureFlagStoreProtocol! @@ -24,7 +22,7 @@ final class MutableStoreTests: XCTestCase { super.setUp() mutableStore = MutableFeatureFlagStore(store: .init()) - resolver = FeatureFlagResolver(configuration: .init(stores: [.mutable(mutableStore)])) + resolver = FeatureFlagResolver(stores: [.mutable(mutableStore)]) } func testOverride() { @@ -80,50 +78,3 @@ final class MutableStoreTests: XCTestCase { } } - -// MARK: - MutableFeatureFlagStore - -final private class MutableFeatureFlagStore: MutableFeatureFlagStoreProtocol { - - private var store: TransparentFeatureFlagStore - private var onSaveChangesClosure: () -> Void - - init(store: TransparentFeatureFlagStore, onSaveChanges: @escaping () -> Void = { }) { - self.store = store - self.onSaveChangesClosure = onSaveChanges - } - - func containsValue(forKey key: String) -> Bool { - store[key] != nil - } - - func value(forKey key: String) -> Value? { - let expectedValueType = Value.self - - switch expectedValueType { - case is Bool.Type, - is Int.Type, - is String.Type, - is Optional.Type, - is Optional.Type, - is Optional.Type: - return store[key] as? Value - default: - assertionFailure("The expected feature flag value type (\(expectedValueType)) is not supported") - return nil - } - } - - func setValue(_ value: Value, forKey key: String) { - store[key] = value - } - - func removeValue(forKey key: String) { - store.removeValue(forKey: key) - } - - func saveChanges() { - onSaveChangesClosure() - } - -} diff --git a/Tests/YMFFTests/RuntimeOverridesStoreTests.swift b/Tests/YMFFTests/Stores/RuntimeOverridesStoreTests.swift similarity index 100% rename from Tests/YMFFTests/RuntimeOverridesStoreTests.swift rename to Tests/YMFFTests/Stores/RuntimeOverridesStoreTests.swift diff --git a/Tests/YMFFTests/UserDefaultsStoreTests.swift b/Tests/YMFFTests/Stores/UserDefaultsStoreTests.swift similarity index 100% rename from Tests/YMFFTests/UserDefaultsStoreTests.swift rename to Tests/YMFFTests/Stores/UserDefaultsStoreTests.swift From c1675f7f9d293e1a18ce7289c82fd571eab28cae Mon Sep 17 00:00:00 2001 From: Yakov Manshin Date: Sat, 5 Jun 2021 13:35:37 +0300 Subject: [PATCH 4/4] [#76] Disallowed Warnings on CI (#78) * Removed `--allow-warnings` from `cocoapods-podspec-lint` --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 4744c08..aac4fcb 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -25,4 +25,4 @@ jobs: steps: - uses: actions/checkout@v2 - name: Lint Podspec - run: pod lib lint --allow-warnings --verbose + run: pod lib lint --verbose