{"id":30275177,"url":"https://github.com/yakovmanshin/ymff","last_synced_at":"2025-08-16T09:13:43.029Z","repository":{"id":39979143,"uuid":"297165750","full_name":"yakovmanshin/YMFF","owner":"yakovmanshin","description":"Feature management made easy.","archived":false,"fork":false,"pushed_at":"2025-03-14T01:28:59.000Z","size":197,"stargazers_count":32,"open_issues_count":9,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-15T00:40:05.256Z","etag":null,"topics":["cocoapods","feature-flags","feature-toggles","ios","ios-framework","macos","property-wrapper","spm","swift","swift-framework","swift-package","swift-package-manager","swift-packages","ymff"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yakovmanshin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"yakovmanshin"}},"created_at":"2020-09-20T21:20:45.000Z","updated_at":"2025-03-09T09:29:44.000Z","dependencies_parsed_at":"2024-02-10T16:32:10.825Z","dependency_job_id":"d29145ae-443b-4db0-8f6d-8091ec52777e","html_url":"https://github.com/yakovmanshin/YMFF","commit_stats":{"total_commits":82,"total_committers":1,"mean_commits":82.0,"dds":0.0,"last_synced_commit":"503c26ee1406774c4484a0e31434ddbaf1351169"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/yakovmanshin/YMFF","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yakovmanshin%2FYMFF","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yakovmanshin%2FYMFF/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yakovmanshin%2FYMFF/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yakovmanshin%2FYMFF/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yakovmanshin","download_url":"https://codeload.github.com/yakovmanshin/YMFF/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yakovmanshin%2FYMFF/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270691566,"owners_count":24629103,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-16T02:00:11.002Z","response_time":91,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocoapods","feature-flags","feature-toggles","ios","ios-framework","macos","property-wrapper","spm","swift","swift-framework","swift-package","swift-package-manager","swift-packages","ymff"],"created_at":"2025-08-16T09:13:41.512Z","updated_at":"2025-08-16T09:13:43.017Z","avatar_url":"https://github.com/yakovmanshin.png","language":"Swift","readme":"# YMFF: Feature management made easy\n\nYMFF is a nice little library that makes managing features with feature flags—and managing feature flags themselves—a bliss, thanks to Swift’s [property wrappers](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers) and (in the future) [macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros).\n\n\u003cdetails\u003e\n\u003csummary\u003eWhy \u0026 How\u003c/summary\u003e\n\nEvery company I worked for needed a way to manage availability of features in the apps already shipped to the users. Surprisingly, [*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.\n\n**I aspire to change that.**\n\nYMFF ships completely ready-to-use, right out of the box: you get everything you need to get started in just a few lines of code.\n\n\u003c/details\u003e\n\n## Installation\n\nI’m sure you know how to install dependencies. YMFF supports both SPM and CocoaPods.\n\n\u003cdetails\u003e\n\u003csummary\u003eNeed Help?\u003c/summary\u003e\n\n### Swift Package Manager (SPM)\n\nTo add YMFF to your project, use Xcode’s built-in support for Swift packages. Click File → Add Package Dependencies, and paste the following URL into the search field:\n\n```\nhttps://github.com/yakovmanshin/YMFF\n```\n\nYou’re then prompted to select the version to install and indicate the desired update policy. I recommend starting with the latest version (it’s selected automatically), and choosing “up to next major” as the update rule. Then select the target you want to link YMFF to. Click Finish—and you’re ready to go!\n\nIf you need to use YMFF in another Swift package, add it to the `Package.swift` file as a dependency:\n\n```swift\n.package(url: \"https://github.com/yakovmanshin/YMFF\", from: \"4.0.0\")\n```\n\n### CocoaPods\n\nYMFF supports installation via [CocoaPods](https://youtu.be/iEAjvNRdZa0), but please keep in mind this support is provided on the best-effort basis.\n\nAdd the following to your Podfile:\n\n```ruby\npod 'YMFF', '~\u003e 4.0'\n```\n\n\u003c/details\u003e\n\n## Setup\n\nYMFF relies on the concept of *feature-flag stores*—“sources of truth” for feature-flag values.\n\n### Firebase Remote Config\n\nFirebase Remote Config is one of the most popular tools to control feature flags remotely. YMFF integrates with Remote Config seamlessly, although with some manual action.\n\n\u003cdetails\u003e\n\u003csummary\u003eTypical Setup\u003c/summary\u003e\n\n```swift\nimport FirebaseRemoteConfig\nimport YMFFProtocols\n\nextension RemoteConfig: SynchronousFeatureFlagStore {\n    \n    public func valueSync\u003cValue\u003e(for key: FeatureFlagKey) -\u003e Result\u003cValue, FeatureFlagStoreError\u003e {\n        // Remote Config returns a default value if the requested key doesn’t exist,\n        // so you need to check the key for existence explicitly.\n        guard allKeys(from: .remote).contains(key) else {\n            return .failure(.valueNotFound)\n        }\n        \n        let remoteConfigValue = self[key]\n        \n        let value: Value?\n        // You need to use different `RemoteConfigValue` methods, depending on the return type.\n        // I know, it doesn’t look fancy.\n        switch Value.self {\n        case is Bool.Type:\n            value = remoteConfigValue.boolValue as? Value\n        case is Data.Type:\n            value = remoteConfigValue.dataValue as? Value\n        case is Double.Type:\n            value = remoteConfigValue.numberValue.doubleValue as? Value\n        case is Int.Type:\n            value = remoteConfigValue.numberValue.intValue as? Value\n        case is String.Type:\n            value = remoteConfigValue.stringValue as? Value\n        default:\n            value = nil\n        }\n        \n        if let value {\n            return .success(value)\n        } else {\n            return .failure(.typeMismatch)\n        }\n    }\n    \n}\n```\n\n`RemoteConfig` is now a valid *feature-flag store*.\n\nAlternatively, you can create a custom wrapper object. That’s what I do in my projects to avoid tight coupling.\n\n\u003c/details\u003e\n\n## Usage\n\n### Declaring Feature Flags\n\nHere’s how you declare feature flags with YMFF:\n\n```swift\nimport YMFF\n\n// For convenience, organize feature flags in a separate namespace using an enum.\nenum FeatureFlags {\n    \n    // `resolver` references one or more feature-flag stores.\n    private static let resolver = FeatureFlagResolver(stores: [MyFeatureFlagStore.shared])\n    \n    // Feature flags are initialized with three pieces of data:\n    // a key string, the default (fallback) value, and the resolver.\n    @FeatureFlag(\"ads_enabled\", default: false, resolver: Self.resolver)\n    static var adsEnabled\n    \n    // Feature flags aren’t limited to booleans. You can use any type of value!\n    @FeatureFlag(\"number_of_banners\", default: 3, resolver: Self.resolver)\n    static var numberOfBanners\n    \n    // Advanced: Sometimes you want to map raw values from the store\n    // to native values used in your app. `MyFeatureFlagStore` below\n    // stores values as strings, while the app uses an enum.\n    // To switch between them, you use a `FeatureFlagValueTransformer`.\n    @FeatureFlag(\n        \"ad_unit_kind\",\n        transformer: FeatureFlagValueTransformer { rawValue in\n            AdUnitKind(rawValue: rawValue)\n        } rawValueFromValue: { value in\n            value.rawValue\n        },\n        default: .image,\n        resolver: Self.resolver\n    )\n    static var adUnitKind\n    \n}\n\n// You can use custom types for feature-flag values.\nenum AdUnitKind: String {\n    case text\n    case image\n    case video\n}\n```\n\n### Reading Values\n\nTo the code that makes use of a feature flag, the flag acts just like the type of its value:\n\n```swift\nif FeatureFlags.adsEnabled {\n    switch FeatureFlags.adUnitKind {\n    case .text:\n        displayAdText()\n    case .image:\n        displayAdBanners(count: FeatureFlags.numberOfBanners)\n    case .video:\n        playAdVideo()\n    }\n}\n```\n\n### Writing Values\n\nYMFF lets you write feature-flag values to mutable stores. It’s as simple as assigning a new value to the flag:\n\n```swift\nFeatureFlags.adsEnabled = true\n```\n\nTo remove the value, you call `removeValueFromMutableStores()` on `FeatureFlag`’s *projected value* (i.e. the `FeatureFlag` instance itself, as opposed to its *wrapped value*):\n\n```swift\n// Here `FeatureFlags.$adsEnabled` has the type `FeatureFlag\u003cBool\u003e`, \n// while `FeatureFlags.adsEnabled` is of type `Bool`.\nFeatureFlags.$adsEnabled.removeValueFromMutableStore()\n```\n\n### `UserDefaults`\n\nYou can use `UserDefaults` to read and write feature-flag values. Your changes will persist when the app restarts.\n\n```swift\nimport YMFF\n\nprivate static let resolver = FeatureFlagResolver(stores: [UserDefaultsStore()])\n```\n\nThat’s it!\n\n### More\n\nYou can browse the source files to learn more about the available options.\n\n## What’s in Store\n\n### [Next-Version Roadmap](https://github.com/yakovmanshin/YMFF/milestone/11)\n* [[#124](https://github.com/yakovmanshin/YMFF/issues/124)] Swift macros for easier setup\n* [[#113](https://github.com/yakovmanshin/YMFF/issues/113)] Thread-safety improvements\n* [[#150](https://github.com/yakovmanshin/YMFF/issues/150)] Support for optional values in `UserDefaultsStore`\n* [[#144](https://github.com/yakovmanshin/YMFF/issues/144)] Minimum compiler version: Swift 5.9 (Xcode 15)\n\nThis version is expected in late 2024, after Swift 6 is released.\n\n### Ideas \u0026 Bug Reports\n\nFeel free to open a new issue if something’s not working—or if you have a suggestion.\n","funding_links":["https://github.com/sponsors/yakovmanshin"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyakovmanshin%2Fymff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyakovmanshin%2Fymff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyakovmanshin%2Fymff/lists"}