{"id":17256542,"url":"https://github.com/rwbutler/featureflags","last_synced_at":"2025-04-05T04:13:07.407Z","repository":{"id":56911164,"uuid":"154693939","full_name":"rwbutler/FeatureFlags","owner":"rwbutler","description":"🚩 Allows developers to configure feature flags, run multiple A/B tests or phase feature roll out using a JSON configuration file.","archived":false,"fork":false,"pushed_at":"2023-03-23T10:28:32.000Z","size":1047,"stargazers_count":600,"open_issues_count":0,"forks_count":25,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-29T03:09:12.793Z","etag":null,"topics":["carthage","config","configuration-json","featureflags","ios","json","mvt-test","remote-config","rollup-feature","swift"],"latest_commit_sha":null,"homepage":"https://medium.com/@rwbutler/feature-flags-a-b-testing-mvt-on-ios-718339ac7aa1","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rwbutler.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-10-25T15:23:18.000Z","updated_at":"2025-02-07T06:38:07.000Z","dependencies_parsed_at":"2024-01-19T09:47:10.651Z","dependency_job_id":null,"html_url":"https://github.com/rwbutler/FeatureFlags","commit_stats":{"total_commits":102,"total_committers":1,"mean_commits":102.0,"dds":0.0,"last_synced_commit":"21b4fa89da010981782352467a8b4a20ea047620"},"previous_names":[],"tags_count":53,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwbutler%2FFeatureFlags","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwbutler%2FFeatureFlags/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwbutler%2FFeatureFlags/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rwbutler%2FFeatureFlags/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rwbutler","download_url":"https://codeload.github.com/rwbutler/FeatureFlags/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247284951,"owners_count":20913704,"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","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":["carthage","config","configuration-json","featureflags","ios","json","mvt-test","remote-config","rollup-feature","swift"],"created_at":"2024-10-15T07:14:40.603Z","updated_at":"2025-04-05T04:13:07.362Z","avatar_url":"https://github.com/rwbutler.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![FeatureFlags](https://raw.githubusercontent.com/rwbutler/FeatureFlags/main/docs/images/feature-flags-banner.png)\n\n[![Build Status](https://img.shields.io/bitrise/577a6c12-fa7f-4d64-b0e3-e47884cb90fe/main?label=build\u0026logo=bitrise\u0026token=27nR7InBsLXisNDfibGPaw)](https://app.bitrise.io/app/577a6c12-fa7f-4d64-b0e3-e47884cb90fe?branch=main)\n[![Version](https://img.shields.io/cocoapods/v/FeatureFlags.svg?style=flat)](http://cocoapods.org/pods/Featureflags)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Maintainability](https://api.codeclimate.com/v1/badges/777e0d2788515e5f61a8/maintainability)](https://codeclimate.com/github/rwbutler/FeatureFlags/maintainability)\n[![License](https://img.shields.io/cocoapods/l/FeatureFlags.svg?style=flat)](http://cocoapods.org/pods/FeatureFlags)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Frwbutler%2FFeatureFlags%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/rwbutler/FeatureFlags)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Frwbutler%2FFeatureFlags%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/rwbutler/FeatureFlags)\n[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)\n\nFeatureFlags makes it easy to configure feature flags, A/B and MVT tests via a JSON file which may be bundled with your app or hosted remotely. For remotely-hosted configuration files, you may enable / disable features without another release to the App Store, update the percentages of users in A/B test groups or even roll out a feature previously under A/B test to 100% of your users once you have decided that the feature is ready for prime time. \n\nTo learn more about how to use FeatureFlags, take a look at the [keynote presentation](https://github.com/rwbutler/FeatureFlags/blob/main/docs/presentations/feature-flags.pdf), check out the [blog post](https://medium.com/@rwbutler/feature-flags-a-b-testing-mvt-on-ios-718339ac7aa1), or make use of the table of contents below:\n\n- [Features](#features)\n- [Installation](#installation)\n\t- [Cocoapods](#cocoapods)\n\t- [Carthage](#carthage)\n\t- [Swift Package Manager](#swift-package-manager)\n- [Usage](#usage)\n\t- [Feature Flags](#feature-flags)\n\t- [A/B Tests](#ab-tests)\n\t- [Feature A/B Tests](#feature-ab-tests)\n\t- [Multivariate (MVT) Tests](#multivariate-mvt-tests)\n\t- [Development Flags](#development-flags)\n\t- [Unlock Flags](#unlock-flags)\n- [Advanced Usage](#advanced-usage)\n\t- [Test Bias](#test-bias)\n\t- [Labels](#labels)\n\t- [Rolling Out Features](#rolling-out-features)\n\t- [QA](#qa)\n\t- [Refreshing Configuration](#refreshing-configuration)\n- [Objective-C](#objective-c)\n- [Author](#author)\n- [License](#license)\n- [Additional Software](#additional-software)\n\t- [Frameworks](#frameworks)\n\t- [Tools](#tools)\n\n## Features\n\n- [x] Feature flags\n- [x] A/B testing and MVT testing\n- [x] Feature A/B testing (where a feature is enabled vs a control group without the feature)\n- [x] Host your feature flags JSON configuration remotely allowing you to enable / disable features without releasing a new version of your app\n- [x] Use an existing JSON file or host an entirely new configuration\n- [x] Adjust the percentages of users in each test group remotely\n- [x] Convert an A/B test into a feature flag once you have decided whether the feature test was a success i.e. rollout a feature to 100% of users\n- [x] Visualize the state of your flags and tests using FeatureFlagsViewController in debug builds of your app\n\n## What's new in FeatureFlags 3.0.0?\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n## Installation\n\n### Cocoapods\n\n[CocoaPods](http://cocoapods.org) is a dependency manager which integrates dependencies into your Xcode workspace. To install it using [RubyGems](https://rubygems.org/) run:\n\n```bash\ngem install cocoapods\n```\n\nTo install FeatureFlags using Cocoapods, simply add the following line to your Podfile:\n\n```ruby\npod \"FeatureFlags\"\n```\n\nThen run the command:\n\n```bash\npod install\n```\n\nFor more information [see here](https://cocoapods.org/#getstarted).\n\n### Carthage\n\nCarthage is a dependency manager which produces a binary for manual integration into your project. It can be installed via [Homebrew](https://brew.sh/) using the commands:\n\n```bash\nbrew update\nbrew install carthage\n```\n\nIn order to integrate FeatureFlags into your project via Carthage, add the following line to your project's Cartfile:\n\n```ogdl\ngithub \"rwbutler/FeatureFlags\"\n```\n\nFrom the macOS Terminal run `carthage update --platform iOS` to build the framework then drag `FeatureFlags.framework` into your Xcode project.\n\nFor more information [see here](https://github.com/Carthage/Carthage#quick-start).\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a dependency manager for Swift modules and is included as part of the build system as of Swift 3.0. It is used to automate the download, compilation and linking of dependencies.\n\nTo include FeatureFlags as a dependency within a Swift package, add the package to the `dependencies` entry in your `Package.swift` file as follows:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/rwbutler/FeatureFlags.git\", from: \"2.0.0\")\n]\n```\n\n## Usage\nWith the framework integrated into your project, the next step is configuration using a JSON file which may be bundled as part of your app or hosted remotely. The JSON file may be newly-created or could be an existing configuration JSON file that you're using already. Simply add a key called `features` at the top level of your file mapping to an array of features as follows:\n\n```json\n{\n    \"features\": []\n}\n```\n\nThe contents of the array depends on the feature flags and tests to be configured.\n\nTo let FeatureFlags know where to find your configuration file:\n\n```swift\nguard let featuresURL = Bundle.main.url(forResource: \"features\", withExtension: \"json\") else { return }\nFeatureFlags.configurationURL = featuresURL\n```\n\nOr:\n\n```swift\nguard let featuresURL = URL(string: \"https://www.exampledomain.com/features.json\") else { return }\nFeatureFlags.configurationURL = featuresURL\n```\n\nIn the event that you opt to host your JSON file remotely, you may provide a bundled fallback as part of your app bundle:\n\n```swift\nguard let fallbackURL = Bundle.main.url(forResource: \"features\", withExtension: \"json\") else { return }\nFeatureFlags.localFallbackConfigurationURL = fallbackURL\n```\n\nYour remotely-hosted JSON file will always take precedence over bundled settings and remotely-defined settings will be cached so that in the eventuality that the user is offline, the last settings retrieved from the network will be applied.\n\n### Feature Flags\n\nIn order to configure a feature flag add a feature object to the features array in your JSON configuration.\n\n```json\n{\n    \"features\": [{\n        \"name\": \"Example Feature Flag\",\n        \"enabled\": false\n    }]\n}\n```\n\nThen add an extension on `Feature.Name` to import your feature flag in code as follows:\n\n```swift\nimport FeatureFlags\n\nextension Feature.Name {\n\tstatic let exampleFeatureFlag = Feature.Name(rawValue: \"Example Feature Flag\")\n}\n```\n\nMake sure that the raw value matches the string in your JSON file. Then call the following to check whether the feature flag is enabled:\n\n```swift\nFeature.isEnabled(.exampleFeatureFlag)) \n```\n\nIf the string specified in your `Feature.Name` extension doesn't match the value in your JSON file, the default value returned is `false`. If you need to check the feature exists, you can write:\n\n```swift\nif let feature = Feature.named(.exampleFeatureFlag) {\n\tprint(\"Feature name -\u003e \\(feature.name)\")\n\tprint(\"Feature enabled -\u003e \\(feature.isEnabled())\")\n}\n```\n\n### A/B Tests\n\nTo configure an A/B test, add the following feature object to the features array in your JSON file:\n\n```json\n{\n\t\"name\": \"Example A/B Test\",\n\t\"enabled\": true,\n\t\"test-variations\": [\"Group A\", \"Group B\"]\n}\n```\n\n* `enabled` indicates whether or not the A/B test is enabled.\n\nThe only difference between a feature flag and an A/B test involves adding an array of test variations. FeatureFlags will assume that you are configuring an A/B test if you add two test variations to the array - add any more and the test will automatically become a multivariate test (MVT).\n\nImport your feature into code with an extension on `Feature.Name`:\n\n```swift\nextension Feature.Name {\n\tstatic let exampleABTest = Feature.Name(rawValue: \"Example A/B Test\")\n}\n```\n\nAnd then use the following to check which group the user has been assigned to:\n\n```swift\nif let test = ABTest(rawValue: .exampleABTest) {\n\tprint(\"Is in group A? -\u003e \\(test.isGroupA())\")\n\tprint(\"Is in group B? -\u003e \\(test.isGroupB())\")\n}\n```\n\nAlternatively, you may prefer the following syntax:\n\n```swift\nif let feature = Feature.named(.exampleABTest) {\n\tprint(\"Feature name -\u003e \\(feature.name)\")\n\tprint(\"Is group A? -\u003e \\(feature.isTestVariation(.groupA))\")\n\tprint(\"Is group B? -\u003e \\(feature.isTestVariation(.groupB))\")\n\tprint(\"Test variation -\u003e \\(feature.testVariation())\")\n}\n```\n\n\n### Feature A/B Tests\n\nA feature A/B test is a subtle variation on (and subtype of) an A/B test. In a generic A/B test you may want to check whether a user has been placed in the blue background or red background test variation. A feature A/B test specifically tests whether the introduction of a new feature is an improvement over a control group without the feature. Thus in a feature A/B test - the feature is either off or on.\n\nTo configure a feature A/B test use the following JSON:\n\n```json\n{\n\t\"name\": \"Example Feature A/B Test\",\n\t\"enabled\": true,\n\t\"test-variations\": [\"Enabled\", \"Disabled\"]\n}\n```\n\n* `enabled` indicates whether or not the A/B test is enabled.\n\n```swift\nextension Feature.Name {\n\tstatic let exampleFeatureABTest = Feature.Name(rawValue: \"Example Feature A/B Test\")\n}\n```\n\nBy naming the test variations `Enabled` and `Disabled`, FeatureFlags knows that your intention is to set up a feature A/B test.\n\nConfiguring a feature A/B test has the advantage over a generic A/B test in that instead of having to write:\n\n```swift\nif let feature = Feature.named(.exampleFeatureABTest) {\n\tprint(\"Feature name -\u003e \\(feature.name)\")\n\tprint(\"Is group A? -\u003e \\(feature.isTestVariation(.enabled))\")\n\tprint(\"Is group B? -\u003e \\(feature.isTestVariation(.disabled))\")\n\tprint(\"Test variation -\u003e \\(feature.testVariation())\")\n}\n```\n\nYou may simply use the following to determine which test group the user has been assigned to:\n\n```swift\nFeature.isEnabled(.exampleFeatureABTest))\n```\n\nOrdinarily using the `Feature.enabled()` method tests to see whether a feature is globally enabled; in this specific instance it will return `true` if the user belongs to the group receiving the new feature and `false` if the user belongs to the control group. Note that this method also return `false` if the `enabled` property is set to `false` in the JSON for this feature i.e. the test is globally disabled.\n\n### Multivariate (MVT) Tests\n\nConfiguration of a multivariate test follows much the same pattern as that of an A/B test. Add the following feature object to the features array in your JSON file:\n\n```json\n{\n\t\"name\": \"Example MVT Test\",\n\t\"enabled\": true,\n\t\"test-variations\": [\"Group A\", \"Group B\", \"Group C\"]\n}\n```\n\n* `enabled` indicates whether or not the MVT test is enabled.\n\nFeatureFlags knows that you are configuring a MVT test if you add more than two test variations to the array. Again, import your feature into code with an extension on `Feature.Name`:\n\n```swift\nextension Feature.Name {\n\tstatic let exampleMVTTest = Feature.Name(rawValue: \"Example MVT Test\")\n}\n```\n\nUsing the following to check which group the user has been assigned to:\n\n```swift\nif let feature = Feature.named(.exampleMVTTest) {\n\tprint(\"Feature name -\u003e \\(feature.name)\")\n\tprint(\"Is group A? -\u003e \\(feature.isTestVariation(.groupA))\")\n\tprint(\"Is group B? -\u003e \\(feature.isTestVariation(.groupB))”)\n\tprint(\"Is group C? -\u003e \\(feature.isTestVariation(.groupC))”)\n\tprint(\"Test variation -\u003e \\(feature.testVariation())”)\n}\n```\n\nYou are free to name your test variations whatever you wish:\n\n```json\n{\n\t\"name\": \"Example MVT Test\",\n\t\"enabled\": true,\n\t\"test-variations\": [\"Red\", \"Green\", \"Blue\"]\n}\n```\n\n* `enabled` indicates whether or not the MVT test is enabled.\n\nSimply create an extension on `Test.Variation` to map your test variations in code:\n\n```swift\nextension Test.Variation {\n\tstatic let red = Test.Variation(rawValue: \"Red\")\n\tstatic let green = Test.Variation(rawValue: \"Green\")\n\tstatic let blue = Test.Variation(rawValue: \"Blue\")\n}\n```\n\nThen check which group the user has been assigned to:\n\n```swift\nif let feature = Feature.named(.exampleMVTTest) {\n\tprint(\"Feature name -\u003e \\(feature.name)\")\n\tprint(\"Is red? -\u003e \\(feature.isTestVariation(.red))\")\n\tprint(\"Is green? -\u003e \\(feature.isTestVariation(.green))\")\n\tprint(\"Is blue? -\u003e \\(feature.isTestVariation(.blue))\")\n\tprint(\"Test variation -\u003e \\(feature.testVariation())\")\n}\n```\n\n### Development Flags\n\nWhen developers speak of feature flags they are often referring to one of two things:\n\n- Remote flags: Allow us to remotely toggle a finished feature on or off and roll it out to a specific group of users.\n- Development flags: Allow to us hide features in development in order to keep code shippable.\n\nWith development flags we never want the code under development to be released to users. \n\nConsider if we were to use a remote feature flag to toggle off an unfinished feature allowing us to release version 1 of our app without the feature present. If subsequently we were to finish the feature as part of version 2 of the app and toggle the feature on then users of version one would experience a partially complete feature as the under development version 1 code is enabled. This is a situation we never want to arise hence FeatureFlags caters for development flags as well as remote flags.\n\nTo mark a feature flag as a development flag, first of all include a bundled JSON file as part of your app containing the `features` key. The JSON file may be an existing file or an entirely new file. Next, having defined your feature flags as part of this file, set the configuration URL to reference this file:\n\n```swift\nguard let featuresURL = Bundle.main.url(forResource: \"features\", withExtension: \"json\") else { return }\nFeatureFlags.configurationURL = featuresURL\n```\n\nOr, if you are already using a remote configuration URL then set the fallback configuration URL instead:\n\n```swift\nguard let fallbackURL = Bundle.main.url(forResource: \"features\", withExtension: \"json\") else { return }\nFeatureFlags.localFallbackConfigurationURL = fallbackURL\n```\n\nSet up your feature flag in JSON as you would do normally but setting the `development` property to `true`:\n\n```json\n{\n    \"features\": [{\n        \"name\": \"Example Feature Flag\",\n        \"development\": true,\n        \"enabled\": true\n    }]\n}\n```\n\nAnd that's it! From now on this feature flag will be considered a development flag and the code behind it will never be released to users even if `enabled` is set.\n\nDevelopment flag code will be shown in the following cases:\n\n- If the `#DEBUG` preprocessor flag is set and the flag is `enabled`.\n- If `FeatureFlags.isDevelopment` is set to `true` (it is up to the developer to set this to `true` when the app is in development - the default value is `false`) and the flag is `enabled`.\n\nIf you need more granular control over code that is in development then you may pass the `isDevelopment` flag when checking whether or not a feature is enabled e.g.:\n\n```swift\nif let feature = Feature.named(.exampleFeatureFlag, isDevelopment: true) {\n\tprint(\"Feature name -\u003e \\(feature.name)\")\n\tprint(\"Feature enabled -\u003e \\(feature.isEnabled())\")\n}\n```\n\nIn this example, the `print` statements will only be executed if `exampleFeatureFlag` is enabled and the app is development (i.e. either `#DEBUG` or `FeatureFlags.isDevelopment` is set).\n\n### Unlock Flags\n\nRegardless of whether a feature flag is controlled locally or remotely, the types of flag above operate at global level i.e. they will enable or disable a feature for all users. But if we want to unlock a feature for individual users following an in-app purchase or as a reward for the completion of some goal then we need a way to unlock the feature and have it remain unlocked - we achieve this with *unlock flags*.\n\nTo configure an unlock flag in your configuration JSON file add the following:\n\n```\n{\n    \"features\": [{\n        \"name\": \"Example Unlock Flag\",\n        \"enabled\": true,\n        \"unlocked\": false\n    }]\n}\n```\n\nThe `unlocked` property indicates to FeatureFlags that this is to be an unlock flag whose default value is `false` i.e. the feature will be locked to begin with. Note that if `enabled` property is set to `false` then the feature will be disabled for all users regardless of whether the feature has been unlocked for a particular user as this property operates at a global level.\n\nIt is possible to query whether or not an unlock flag is unlocked as follows:\n\n```\nif let feature = Feature.named(.exampleUnlockFlag) {\n    print(\"Is unlocked? -\u003e \\(feature.isUnlocked())\")\n}\n```\n\nWhen you wish to unlock a feature for the user, call `feature.unlock()`. Conversely, if you only wish to unlock a feature for a certain period of time e.g. to allow the user to trial a feature, you may later call `feature.lock()` to make the feature unavailable again. Both methods return a `Bool` to indicate whether or not the feature has been unlocked / locked. \n\n```\nif let feature = Feature.named(.exampleUnlockFlag) {\n    print(\"Is unlocked? -\u003e \\(feature.unlock())\")\n}\n```\n\nNote that a feature may not be unlocked if:\n\n* The feature flag is not an unlock flag i.e. the `unlocked` property was not defined in JSON.\n* The `enabled` property is set to `false`.\n\n## Advanced Usage\n### Test Bias\nBy default for any A/B or MVT test, the user is equally likely to be assigned each of the specified test variations i.e. for an A/B test, there's a 50%/50% chance of being assigned one group or another. For a MVT test with four variations, the chance of being assigned to each is 25%.\n\nIt is possible to configure a test bias such that the likelihood of being assigned to each test variation is not equal. To do so, simply add the following JSON to your feature object:\n\n```json\n{\n\t\"features\": [{\n\t\t\"name\": \"Example A/B Test\",\n\t\t\"enabled\": true,\n\t\t\"test-biases\": [80, 20],\n\t\t\"test-variations\": [\"Group A\", \"Group B\"]\n\t}]\n}\n```\n\nThe number of weightings specified in the `test-biases` array must be equal to the number of test variations and must amount to 100 otherwise the weightings will be ignored and default to equal weightings.\n\n### Labels\nIt is possible to attach labels to test variations in case you wish to send analytics respective to the test group to which a user has been assigned.\n\nTo do so, define an array of `labels` of equal length to the number of test variations specified:\n\n```json\n{\n\t\"features\": [{\n\t\t\"name\": \"Example A/B Test\",\n\t\t\"enabled\": true,\n\t\t\"test-biases\": [50, 50],\n\t\t\"test-variations\": [\"Group A\", \"Group B\"],\n\t\t\"labels\": [\"label1-for-analytics\", \"label2-for-analytics\"]\n\t}]\n}\n```\n\nThen to retrieve your labels in code you would write the following:\n\n```swift\nif let feature = Feature.named(.exampleABTest) {\n\tprint(\"Group A label -\u003e \\(feature.label(.groupA))\")\n\tprint(\"Group B label -\u003e \\(feature.label(.groupB))\")\n}\n```\n\n### Rolling Out Features\n\nThe most powerful feature of the FeatureFlags framework is the ability to adjust the test biases in your remote JSON configuration file and have the users automatically be re-assigned to new test groups. For example, you might decide to roll out a feature using a 10%/90% (whereby 10% of users receive the new feature) split in the first week, 20%/80% in the second week and so on. \n\nSimply update the weightings in the `test-biases` array and the next time the framework checks your JSON configuration, groups will be re-assigned.\n\nWhen you are done A/B or MVT testing a feature you will have gathered enough analytics to decide whether or not to roll out the feature to your entire user base. At this point, you may decide to disable the feature entirely by setting the `enabled` flag to `false` in your JSON file or in the case of a successful test, you may decide to roll out the feature to all users by adjusting the feature object in your JSON file from:\n\n```json\n{\n\t\"features\": [{\n\t\t\"name\": \"Example A/B Test\",\n\t\t\"enabled\": true,\n\t\t\"test-biases\": [50, 50],\n\t\t\"test-variations\": [\"Group A\", \"Group B\"],\n\t\t\"labels\": [\"label1-for-analytics\", \"label2-for-analytics\"]\n\t}]\n}\n```\n\n\nTo a feature flag globally enabled for all users as follows:\n\n```json\n{\n\t\"features\": [{\n\t\t\"name\": \"Example A/B Test\",\n\t\t\"enabled\": true\n\t}]\n}\n```\n\n### QA\nIn order to test that both variations of your new feature work correctly you may need to adjust the status of your feature flags / tests at runtime. To this end FeatureFlags provides the `FeatureFlagsViewController` which allows you to toggle features flags on/off in debug builds of your app or cycle A/B testing or MVT testing variations.\n\nTo display the view controller specify the navigational preferences desired and then push the view controller by providing a `UINavigationController`:\n\n```swift\n let navigationSettings = FeatureFlagsViewControllerNavigationSettings(autoClose: true, closeButtonAlignment: .right, closeButton: .save, isNavigationBarHidden: false)\n \nFeatureFlags.pushFeatureFlags(delegate: self, navigationController: navigationController, navigationSettings: navigationSettings)\n```\n\n\n![FeatureFlagsViewController](https://raw.githubusercontent.com/rwbutler/FeatureFlags/main/docs/images/feature-flags-view-controller.png)\n\nShould you need further information on the state of each feature flag / test, you may use 3D Touch to peek / pop more information.\n\n![FeatureDetailsViewController](https://raw.githubusercontent.com/rwbutler/FeatureFlags/main/docs/images/feature-details-view-controller.png)\n\n### Refreshing Configuration\n\nShould you need to refresh your configuration at any time you may call `FeatureFlags.refresh()` which optionally accepts a completion closure to notify you when the refresh is complete.\n\nIf you have opted to include your feature flag information as part of an existing JSON file which your app has already fetched you may wish to use the following method passing the JSON file data to avoid repeated network calls:\n\n```swift\nFeatureFlags.refreshWithData(_:completion:) \n```\n\n## Objective-C\n\nWhilst FeatureFlags is primarily intended for use by Swift apps, should the need arise to check whether a feature flag is enabled in Objective-C it is possible to do so as follows:\n\n```objc\nstatic NSString *const kMyNewFeatureFlag = @\"My New Feature Flag\";\n\nif (FEATURE_IS_ENABLED(kMyNewFeatureFlag)) {\n    ...\n}\n```\n\n## Author\n\n[Ross Butler](https://github.com/rwbutler)\n\n## License\n\nFeatureFlags is available under the MIT license. See the [LICENSE file](./LICENSE) for more info.\n\n## Additional Software\n\n### Controls\n\n* [AnimatedGradientView](https://github.com/rwbutler/AnimatedGradientView) - Powerful gradient animations made simple for iOS.\n\n|[AnimatedGradientView](https://github.com/rwbutler/AnimatedGradientView) |\n|:-------------------------:|\n|[![AnimatedGradientView](https://raw.githubusercontent.com/rwbutler/AnimatedGradientView/master/docs/images/animated-gradient-view-logo.png)](https://github.com/rwbutler/AnimatedGradientView) \n\n### Frameworks\n\n* [Cheats](https://github.com/rwbutler/Cheats) - Retro cheat codes for modern iOS apps.\n* [Connectivity](https://github.com/rwbutler/Connectivity) - Improves on Reachability for determining Internet connectivity in your iOS application.\n* [FeatureFlags](https://github.com/rwbutler/FeatureFlags) - Allows developers to configure feature flags, run multiple A/B or MVT tests using a bundled / remotely-hosted JSON configuration file.\n* [FlexibleRowHeightGridLayout](https://github.com/rwbutler/FlexibleRowHeightGridLayout) - A UICollectionView grid layout designed to support Dynamic Type by allowing the height of each row to size to fit content.\n* [Hash](https://github.com/rwbutler/Hash) - Lightweight means of generating message digests and HMACs using popular hash functions including MD5, SHA-1, SHA-256.\n* [Skylark](https://github.com/rwbutler/Skylark) - Fully Swift BDD testing framework for writing Cucumber scenarios using Gherkin syntax.\n* [TailorSwift](https://github.com/rwbutler/TailorSwift) - A collection of useful Swift Core Library / Foundation framework extensions.\n* [TypographyKit](https://github.com/rwbutler/TypographyKit) - Consistent \u0026 accessible visual styling on iOS with Dynamic Type support.\n* [Updates](https://github.com/rwbutler/Updates) - Automatically detects app updates and gently prompts users to update.\n\n|[Cheats](https://github.com/rwbutler/Cheats) |[Connectivity](https://github.com/rwbutler/Connectivity) | [FeatureFlags](https://github.com/rwbutler/FeatureFlags) | [Skylark](https://github.com/rwbutler/Skylark) | [TypographyKit](https://github.com/rwbutler/TypographyKit) | [Updates](https://github.com/rwbutler/Updates) |\n|:-------------------------:|:-------------------------:|:-------------------------:|:-------------------------:|:-------------------------:|:-------------------------:|\n|[![Cheats](https://raw.githubusercontent.com/rwbutler/Cheats/master/docs/images/cheats-logo.png)](https://github.com/rwbutler/Cheats) |[![Connectivity](https://github.com/rwbutler/Connectivity/raw/main/ConnectivityLogo.png)](https://github.com/rwbutler/Connectivity) | [![FeatureFlags](https://raw.githubusercontent.com/rwbutler/FeatureFlags/main/docs/images/feature-flags-logo.png)](https://github.com/rwbutler/FeatureFlags) | [![Skylark](https://github.com/rwbutler/Skylark/raw/master/SkylarkLogo.png)](https://github.com/rwbutler/Skylark) | [![TypographyKit](https://raw.githubusercontent.com/rwbutler/TypographyKit/main/docs/images/typography-kit-logo.png)](https://github.com/rwbutler/TypographyKit) | [![Updates](https://raw.githubusercontent.com/rwbutler/Updates/master/docs/images/updates-logo.png)](https://github.com/rwbutler/Updates)\n\n### Tools\n\n* [Clear DerivedData](https://github.com/rwbutler/ClearDerivedData) - Utility to quickly clear your DerivedData directory simply by typing `cdd` from the Terminal.\n* [Config Validator](https://github.com/rwbutler/ConfigValidator) - Config Validator validates \u0026 uploads your configuration files and cache clears your CDN as part of your CI process.\n* [IPA Uploader](https://github.com/rwbutler/IPAUploader) - Uploads your apps to TestFlight \u0026 App Store.\n* [Palette](https://github.com/rwbutler/TypographyKitPalette) - Makes your [TypographyKit](https://github.com/rwbutler/TypographyKit) color palette available in Xcode Interface Builder.\n\n|[Config Validator](https://github.com/rwbutler/ConfigValidator) | [IPA Uploader](https://github.com/rwbutler/IPAUploader) | [Palette](https://github.com/rwbutler/TypographyKitPalette)|\n|:-------------------------:|:-------------------------:|:-------------------------:|\n|[![Config Validator](https://raw.githubusercontent.com/rwbutler/ConfigValidator/master/docs/images/config-validator-logo.png)](https://github.com/rwbutler/ConfigValidator) | [![IPA Uploader](https://raw.githubusercontent.com/rwbutler/IPAUploader/master/docs/images/ipa-uploader-logo.png)](https://github.com/rwbutler/IPAUploader) | [![Palette](https://raw.githubusercontent.com/rwbutler/TypographyKitPalette/master/docs/images/typography-kit-palette-logo.png)](https://github.com/rwbutler/TypographyKitPalette)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwbutler%2Ffeatureflags","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frwbutler%2Ffeatureflags","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwbutler%2Ffeatureflags/lists"}