Compare commits

...

11 Commits

Author SHA1 Message Date
Yakov Manshin aca7cdaf6d
[#106] Updated Minimum Deployment Targets (#117)
* Updated the minimum iOS deployment target to 13
* Updated the minimum macOS deployment target to 10.15 (Catalina)
2023-01-17 21:55:01 +01:00
Yakov Manshin 470177543a
[#104] Updated Minimum Swift Version to 5.5 (#116)
* Updated the minimum Swift compiler version to 5.5
* Removed testing with Swift 5.3 from the GHA workflow
2023-01-17 21:55:01 +01:00
Yakov Manshin a8e34b17d0
Switched CI to Xcode 14.1 (#115)
* Updated the GitHub Actions workflows to use Xcode 14.1

Signed-off-by: Yakov Manshin <git@yakovmanshin.com>
2023-01-17 21:55:01 +01:00
Yakov Manshin 4ca114eab4
[#109] Updated Version to 3.1.0 (#111) 2023-01-17 21:55:01 +01:00
Yakov Manshin d13bbb2c58
[#107] Updated `README` (#108)
* Moved some paragraphs to collapsible sections
* Added v4 roadmap
2023-01-17 21:55:01 +01:00
Yakov Manshin c708f79588
[#81] Push to CocoaPods Trunk on Release (#105)
* Introduced the `Release` GHA workflow
* Updated the `Common` workflow
2023-01-17 21:54:54 +01:00
Yakov Manshin a368a02ee7
Added Availability Attributes 2022-10-05 06:21:10 +02:00
Yakov Manshin fbe154665f
`AsyncMutableStoreTests` Stub 2022-10-05 05:14:17 +02:00
Yakov Manshin dbf6cd9d1a
`AsyncMutableFeatureFlagStoreMock` 2022-10-05 05:14:17 +02:00
Yakov Manshin 9b2454e783
`AsyncMutableFeatureFlagStoreProtocol` 2022-10-05 05:14:17 +02:00
Yakov Manshin df6a034bc5
`AsyncFeatureFlagStoreProtocol` 2022-10-05 05:14:16 +02:00
9 changed files with 224 additions and 19 deletions

View File

@ -4,26 +4,28 @@ on:
push:
branches:
- "main"
workflow_call:
inputs:
force_all_checks:
type: boolean
required: true
workflow_dispatch:
env:
XCODE_PATH: "/Applications/Xcode_14.0.1.app"
XCODE_PATH: "/Applications/Xcode_14.1.app"
jobs:
spm_tests_macos:
name: SPM Tests (Swift ${{ matrix.SWIFT_VERSION }} on macOS)
runs-on: ${{ matrix.OS }}
strategy:
matrix:
SWIFT_VERSION: ["5.7", "5.5", "5.3"]
SWIFT_VERSION: ["5.7", "5.5"]
include:
- SWIFT_VERSION: "5.7"
OS: macOS-12
XCODE_APP_NAME: "Xcode_14.0.1"
XCODE_APP_NAME: "Xcode_14.1"
- SWIFT_VERSION: "5.5"
OS: macOS-12
XCODE_APP_NAME: "Xcode_13.2.1"
- SWIFT_VERSION: "5.3"
OS: macOS-11
XCODE_APP_NAME: "Xcode_12.4"
steps:
- uses: actions/checkout@v3
- name: Select Xcode Version
@ -44,7 +46,7 @@ jobs:
cocoapods_podspec_lint:
name: CocoaPods Podspec Linting
runs-on: macOS-12
if: ${{ github.event_name == 'pull_request' }}
if: ${{ inputs.force_all_checks || github.event_name == 'pull_request' }}
needs: [spm_tests_macos, spm_tests_linux]
steps:
- uses: actions/checkout@v3

24
.github/workflows/Release.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Release
on:
release:
types: [published]
env:
XCODE_PATH: "/Applications/Xcode_14.1.app"
jobs:
run_common_checks:
uses: ./.github/workflows/Common.yml
with:
force_all_checks: true
cocoapods_trunk_push:
name: Push to CocoaPods Trunk
runs-on: macOS-12
needs: run_common_checks
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TOKEN }}
steps:
- uses: actions/checkout@v3
- name: Select Xcode Version
run: sudo xcode-select -switch ${{ env.XCODE_PATH }}
- name: Push
run: |
pod trunk push YMFF.podspec --verbose

View File

@ -1,12 +1,12 @@
// swift-tools-version:5.3
// swift-tools-version: 5.5
import PackageDescription
let package = Package(
name: "YMFF",
platforms: [
.iOS(.v11),
.macOS(.v10_13),
.iOS(.v13),
.macOS(.v10_15),
],
products: [
.library(

View File

@ -1,12 +1,17 @@
# YMFF: Feature management made easy
YMFF is a nice little library that makes managing features with feature flags—and managing feature flags themselves—a bliss, thanks mainly to the power of Swifts [property wrappers](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617).
<details>
<summary>Why & How</summary>
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 managing features with feature flags—and managing feature flags themselves—a bliss, thanks mainly to the power of Swifts [property wrappers](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617).
YMFF ships completely ready-to-use, right out of the box: you get everything you need to get started 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).
YMFF ships completely ready for use, right out of the box: you get everything you need to get started 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).
</details>
## Installation
@ -22,7 +27,7 @@ Youre then prompted to select the version to install and indicate the desired
If you need to use YMFF in another Swift package, add it to the `Package.swift` file as a dependency:
```swift
.package(url: "https://github.com/yakovmanshin/YMFF", .upToNextMajor(from: "3.0.0"))
.package(url: "https://github.com/yakovmanshin/YMFF", .upToNextMajor(from: "3.1.0"))
```
### CocoaPods
@ -31,7 +36,7 @@ YMFF alternatively supports installation via [CocoaPods](https://youtu.be/iEAjvN
Add the following to your Podfile:
```ruby
pod 'YMFF', '~> 3.0'
pod 'YMFF', '~> 3.1'
```
## Setup
@ -40,6 +45,9 @@ All you need to start managing features with YMFF is at least one *feature flag
### Firebase Remote Config
Firebases Remote Config is one of the most popular tools to manage feature flags on the server side. Remote Configs `RemoteConfigValue` requires the use of different methods to retrieve values of different types. Integration of YMFF with Remote Config, although doesnt look very pretty, is quite straightforward.
<details>
<summary>Typical Setup</summary>
```swift
import FirebaseRemoteConfig
import YMFFProtocols
@ -82,6 +90,8 @@ Now, `RemoteConfig` is a valid *feature flag store*.
Alternatively, you can create a custom wrapper object. Thats what I tend to do in my projects to achieve greater flexibility and avoid tight coupling.
</details>
## Usage
Heres the most basic way to use YMFF:
@ -184,6 +194,13 @@ Thats it!
You can browse the source files to learn more about the options available to you. An autogenerated documentation is available at [opensource.ym.dev](https://opensource.ym.dev/YMFF/).
## v4 Roadmap
* [[#96](https://github.com/yakovmanshin/YMFF/issues/96)] Support for asynchronous feature-flag stores
* [[#104](https://github.com/yakovmanshin/YMFF/issues/104)] Minimum compiler version: Swift 5.5 (Xcode 13)
* [[#106](https://github.com/yakovmanshin/YMFF/issues/106)] Minimum deployment target: iOS 13, macOS 10.15
YMFF v4 is expected to be released in late 2022.
## Contributing
Contributions are welcome!
@ -192,6 +209,6 @@ Have a look at [issues](https://github.com/yakovmanshin/YMFF/issues) to see the
If youd like to discuss something else, contact [me](https://github.com/yakovmanshin) via email (the address is in the profile).
## License and Copyright
YMFF is available under the terms of the Apache License, version 2.0. See the [LICENSE file](https://github.com/yakovmanshin/YMFF/blob/main/LICENSE) for details.
YMFF is licensed under the Apache License. See the [LICENSE file](https://github.com/yakovmanshin/YMFF/blob/main/LICENSE) for details.
© 20202022 Yakov Manshin

View File

@ -0,0 +1,19 @@
//
// AsyncFeatureFlagStoreProtocol.swift
// YMFFProtocols
//
// Created by Yakov Manshin on 9/29/22.
// Copyright © 2022 Yakov Manshin. See the LICENSE file for license info.
//
#if swift(>=5.5)
@available(iOS 13, *)
@available(macOS 10.15, *)
public protocol AsyncFeatureFlagStoreProtocol {
func value<Value>(forKey key: String) async throws -> Value
}
#endif

View File

@ -0,0 +1,33 @@
//
// AsyncMutableFeatureFlagStoreProtocol.swift
// YMFFProtocols
//
// Created by Yakov Manshin on 9/29/22.
// Copyright © 2022 Yakov Manshin. See the LICENSE file for license info.
//
#if swift(>=5.5)
@available(iOS 13, *)
@available(macOS 10.15, *)
public protocol AsyncMutableFeatureFlagStoreProtocol: AnyObject, AsyncFeatureFlagStoreProtocol {
func setValue<Value>(_ value: Value, forKey key: String) async throws
func removeValue(forKey key: String) async throws
func saveChanges() async throws
}
// MARK: - Default Implementation
@available(iOS 13, *)
@available(macOS 10.15, *)
extension AsyncMutableFeatureFlagStoreProtocol {
public func saveChanges() async throws { }
}
#endif

View File

@ -0,0 +1,78 @@
//
// AsyncMutableFeatureFlagStoreMock.swift
// YMFFTests
//
// Created by Yakov Manshin on 9/29/22.
// Copyright © 2022 Yakov Manshin. See the LICENSE file for license info.
//
#if swift(>=5.5)
import YMFF
#if !COCOAPODS
import YMFFProtocols
#endif
// MARK: - Mock
final class AsyncMutableFeatureFlagStoreMock: AsyncMutableFeatureFlagStoreProtocol {
private var store: TransparentFeatureFlagStore
private var onSaveChangesClosure: () -> Void
init(store: TransparentFeatureFlagStore, onSaveChanges: @escaping () -> Void = { }) {
self.store = store
self.onSaveChangesClosure = onSaveChanges
}
func value<Value>(forKey key: String) async throws -> Value {
// Wait for 0.1 second
try await Task.sleep(nanoseconds: 1_000_000)
guard store[key] != nil else {
throw AsyncMutableFeatureFlagStoreMockError.valueNotFoundInStore
}
guard let value = store[key] as? Value else {
throw AsyncMutableFeatureFlagStoreMockError.valueTypeMismatch
}
return value
}
func setValue<Value>(_ value: Value, forKey key: String) async throws {
// Wait for 0.1 second
try await Task.sleep(nanoseconds: 1_000_000)
if let existingValue = store[key], type(of: existingValue) != type(of: value) {
throw AsyncMutableFeatureFlagStoreMockError.valueTypeMismatch
}
store[key] = value
}
func removeValue(forKey key: String) async throws {
// Wait for 0.1 second
try await Task.sleep(nanoseconds: 1_000_000)
guard store[key] != nil else {
throw AsyncMutableFeatureFlagStoreMockError.valueNotFoundInStore
}
store[key] = nil
}
func saveChanges() async throws {
onSaveChangesClosure()
}
}
// MARK: - Error
fileprivate enum AsyncMutableFeatureFlagStoreMockError: Error {
case valueNotFoundInStore
case valueTypeMismatch
}
#endif

View File

@ -0,0 +1,32 @@
//
// AsyncMutableStoreTests.swift
// YMFFTests
//
// Created by Yakov Manshin on 9/30/22.
// Copyright © 2022 Yakov Manshin. See the LICENSE file for license info.
//
#if swift(>=5.5)
@testable import YMFF
import XCTest
#if !COCOAPODS
import YMFFProtocols
#endif
final class AsyncMutableStoreTests: XCTestCase {
private var asyncMutableStore: AsyncMutableFeatureFlagStoreProtocol!
private var resolver: FeatureFlagResolverProtocol!
override func setUp() async throws {
try await super.setUp()
asyncMutableStore = AsyncMutableFeatureFlagStoreMock(store: .init())
resolver = FeatureFlagResolver(stores: [])
}
}
#endif

View File

@ -3,8 +3,8 @@ Pod::Spec.new do |s|
# Root
s.name = "YMFF"
s.version = "3.0.0"
s.swift_version = "5.3"
s.version = "3.1.0"
s.swift_version = "5.5"
s.authors = { "Yakov Manshin" => "git@yakovmanshin.com" }
s.social_media_url = "https://twitter.com/yakovmanshin"
s.license = { :type => "Apache License, version 2.0", :file => "LICENSE" }
@ -20,8 +20,8 @@ Pod::Spec.new do |s|
# Platform
s.osx.deployment_target = "10.13"
s.ios.deployment_target = "11.0"
s.ios.deployment_target = "13.0"
s.osx.deployment_target = "10.15"
# Subspecs