Getting Started With the Swift iOS Feature Flag

Written by: amanda
10 min read

Mobile users expect more. They want their applications to be as stable as yesterday while looking and acting more and more like tomorrow. How do you keep up? How can you reliably test and push new features without risking a disastrous release?

Feature toggles (also known as feature flags) are one way. They provide app developers with a way to add new behavior to an application and then enable or disable it without deploying a new release. So how do feature toggles work? Add some new behavior to the application, wrap it in a boolean, default it to "off," and then flip it when it's safe, right?

There's more to it than that. Effectively using feature flags requires understanding feature flag strategy and tactics. I’ll get you to your first iOS feature flag quickly, and then give you a deeper understanding of feature flags and how to avoid creating technical debt when you are using them.

This tutorial assumes familiarity with basic iOS concepts and the ability to set up and use Xcode. While I'll use an iPhone simulator for testing the application, you can use whatever method you feel most comfortable with. 

Summary

Here's an overview of what will cover in the post:

  • A Basic Feature Toggle

  • An iOS Feature Toggle

  • Getting Started With CloudBees Feature Management

  • Extra Credit: Configurations

  • Conclusion

A Basic Feature Toggle

Let's start with the basic single view iOS application offered by Xcode 9.

We'll add a Label to the application's single view like below:

We'll add the label to the viewDidLoad() method of our ViewController, with the name GreetingLabe, and assign a greeting to it.

    @IBOutlet weak var greetingLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        greetingLabel.text = "Hello, World!"
    }

We could set the default value in the UILabel definition if we want, but we'll define it explicitly throughout the tutorial to make the behavior easy to see. We don't need to make any further changes to the sample code. Let's build and run the application.

We have a basic iOS app to work with. We'll use feature toggles to modify the application's greeting.

An iOS Feature Toggle

So let’s imagine we want to replace our greeting with a different one depending on the season. A fundamental mechanism would be a boolean flag.

var isNewYear = true

@IBOutlet weak var greetingLabel: UILabel!
override func viewDidLoad() {
    super.viewDidLoad()

    if isNewYear {
        greetingLabel.text = "Happy New Year!"
    } else {
        greetingLabel.text = "Hello, World!"
    }
}

When we build and run this version we see the new greeting below:

And we have our first feature toggle! But this means modifying, building, and getting a release through the App Store whenever we want to change our greeting. Modifying a configuration file would be a bit simpler and shave a little bit of time and effort off the release cycle.  Let's create a property list and add our toggle in the form of a configuration property to it.

We have a property list named featureflag.plist. In it, there is one parameter: a boolean flag named isNewYear. We'll load it and use the property to set our flag. We'll load the file in the controller, even though that's not what we would do in production code, to keep things simple.

@IBOutlet weak var greetingLabel: UILabel!
override func viewDidLoad() {
    super.viewDidLoad()

    var myDict: NSDictionary?
    if let path = Bundle.main.path(forResource: "featureflag", ofType: "plist") {
        myDict = NSDictionary(contentsOfFile: path)
    }

    isNewYear = myDict?["isNewYear"] as! Bool

    if isNewYear {
        greetingLabel.text = "Happy New Year!"
    } else {
        greetingLabel.text = "Hello, World!"
    }
}

Now we can toggle the value of isNewYear to YES or NO via the properties file, and we'll see the proper greeting. The plist gets us out of modifying code when we want to toggle a feature, but we still need to build and release. Let’s look at a better way to change, and more importantly, manage feature toggles.

Getting Started With CloudBees Feature Management

First, you’ll need to create a free CloudBees Feature Management account here. Once that's done, sign in to your CloudBees Feature Management account and create a new application.

Be sure to select iOS, as shown above. Next, you’ll see instructions for adding CloudBees Feature Management to your application.

First, download rox.zip, unzip it, and add the rox directory to your project's directory tree. Next, add RoxSdk.xcodeproj and ROXCore.framework to your project. Then, select Add Files to... from the Project Navigator.

Now, select the files from the RoxSdk directory in the zip file output.

You'll see ROXCore.framework icon in the Project Navigator. Add those files. Next, we need to remove the framework from the project's linked frameworks. Select your project's name at the top of Navigator tab.

And scroll down in the application information page to Linked Frameworks and Libraries.

    Next, select ROXCore.framework and then click the minus to remove it. We want to add it back to embedded binaries. Click the plus in Embedded Binaries.

Then, select ROXCore.framework and ROX.frameworkiOS and click Add. Last, we need to set up a build step to strip out unneeded libraries from our production application. First, add a script build phase:

Then locate the strip_frameworks.sh script, and drag it into the script target:

Initialize the API

The Xcode project is ready now, and we can initialize the API. Finally, click Setup SDK back at the CloudBees Feature Management dashboard.

Add the import ROX declaration at the top of AppDelegate.swift. ROllout assigned a unique initialization key to our application. Add ROX.setup() to didFinishLaunchingWithOptions. The top of AppDelegate.swift should look similar to this:

//
//  AppDelegate.swift
//  Feature Flag IOS Example
//
// Created by Eric Goebelbecker on 9/12/22. 
// Copyright © 2022 Eric Goebelbecker. All rights reserved.
//
import UIKit
import ROX

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        ROX.setup(withKey:"<YOURAPPKEY>")
        return true
    }

Now we can run the application to initialize CloudBees Feature Management. Click Activate App on the CloudBees Feature Management dashboard. After the application builds and runs, you are rewarded with this:

Add a Managed Feature Flag

Let's create a feature flag and register it with CloudBees Feature Management. If we look at our CloudBees Feature Management dashboard and check flags, we see this:

There are no flags, and there is no way to add one. That's because we create them from our application, not from the dashboard. In Swift, feature flags are RoxFlags, and are managed in RoxContainers. First, add this class to the project:

import Foundation
import ROX
public class Flags: RoxContainer {
    public let isNewyear = RoxFlag(withDefault: true);
}

We've created a single flag named isNewYear with a default value of true. We register the container with the API before calling Rox.setup(). Make this change to AppDelegate:

let flags = Flags()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        ROX.register("Flags", container: flags)
        ROX.setup(withKey:"yourappkey")
        // Override point for customization after application launch.
        return true
    }

We're registering the container with CloudBees Feature Management. The first argument to register() is a name that rollout will associate with it. Run the application and then go to the Flags menu item in the CloudBees Feature Management dashboard.

The new flag is there. We see the container name and the flag name in the dashboard.

Reading a Managed Feature Flag

Next, we need to update our code to use the RoxFlag instead of the property list. CloudBees Feature Management retrieves feature flag values asynchronously and caches them. It extracts the latest value from the cache on startup while initiating a fetch from the network in parallel. This means that the first time we run our application, we will immediately get the default value for our flag (false for toggles unless we override), and then the value set at CloudBees Feature Management upstream will be retrieved sometime later. Since we want to see the value of the flag before we load our view, we need to do a little bit of extra work. This will also teach us a bit more about the CloudBees Feature Management API. We're going to add something to our call to ROX.setup().

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    let options = RoxOptions()
    options.onConfigurationFetched = { (result: RoxFetcherResult) -> Void in
        NotificationCenter.default.post(name: Notification.Name(rawValue: "update"), object: nil)
    };

    ROX.register("FeatureFlags", container: flags)
    ROX.setup(withKey:"5b0755c73f389f1cbf68639d", options:options)
    return true
}

First, we create a RoxOptions. This object holds configuration settings that we can pass to the API when we initialize it. One of those options, onConfigurationFetched, is a callback for when the latest configuration is successfully fetched from CloudBees Feature Management. When our callback is executed, we use NotificationCenter to notify any interested objects that new information has been retrieved. There are a few advantages to using NotificationCenter instead of trying to call our view directly:

  • Since this callback will be called on startup, our ViewController probably hasn't been created yet.

  • If we add more controllers to the application, later things will start to get complicated.

  • Threading issues are delegated to listeners (as we'll see).

Next, we need to make some changes in ViewController.swift.

@IBOutlet weak var greetingLabel: UILabel!
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(updateLabels), name: NSNotification.Name(rawValue: "update"), object: nil)
}

@objc func updateLabels() {
    DispatchQueue.main.async {
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        if (appDelegate.flags.isNewYear.isEnabled) {
            self.greetingLabel.text = "Happy New Year!"
        } else {
            self.greetingLabel.text = "Hello, World!"
        }
    }
}

ViewDidLoad() is now subscribing to update events and delegating them to a method named UpdateLabels() We moved setting the value of our label to UpdateLabels(). In it, we get a reference to AppDelegate so we can access its copy of the flags and check them. However, we have to dispatch this task to the main thread because only the UI thread can modify the view. We check the value of isNewYear and set the label appropriately. CloudBees Feature ManagementFlags() also has an enabled block, but since we need to set the label back if the flag is toggled from "on "to "off," the if/else logic makes more sense.  

Managing a Feature Flag

We manage flags by adding them to experiments. An experiment is a scheme for controlling flags (and other variables) in production. First, click on Production in the left-hand side menu and then click Experiments. This will bring up a screen with a Create Experiment button. Click that and fill out the new experiment window appropriately.

Select Set Audience.

And we see a console for setting flags to true, false, or split. Leave it set to False. If we run our application now, we see "Hello, World!" Next, change it to True and restart the application.

We see the Happy New Year greeting! Flip it back to false and click the phone's home button. Then tap on the application icon to open it again. It flips back to Hello World!  We can change the behavior of the application without touching code and releasing a new version. We can even alter it while the application is running on a client's device. Before we move on, let’s take a look at the experiment on the console again. Flip the flag from true to split.

This option allows us to distribute a change to a percentage of users, hence the term "experiment." If you play with it now, you'll need to completely start and stop the application to see different settings to overcome the API's caching. This is the power of feature flags!

Extra Credit: Configurations

Tying the contents of a label to a true/false flag is an admittedly contrived example, but it kept the tutorial code focused and simple. Let's take a look at another mechanism for setting a label. CloudBees Feature Management supports managing configuration values from the dashboard. Let's add a new field to Flags.

public class Flags: RoxContainer{
    public let isNewYear = RoxFlag()
    public let greetingText = RoxConfigurationString(defaultValue: "Hello, World!")
}

RoxConfigurationString is exactly what it sounds like. Build and run the application again, and then go back to the CloudBees Feature Management dashboard. Under Production and Configurations, we see our new value.

If we click on it, we can set it to a value like so:

Select >= for the criteria, and enter Happy New Year! for the value. Then we need to make a small code change. Change updateLabels() to use the value of our new member:

func updateLabels() {

    DispatchQueue.main.async {
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        self.greetingLabel.text = appDelegate.flags.greetingText.value
    }
}

And run the app. We see Happy New Year! Let's change it.


Close and open the application. No need to restart or rebuild: our notification mechanism with handle the change.

We can change our application config with CloudBees Feature Management!

Conclusion

This guide demonstrates how to get started with CloudBees Feature Management with iOS and Swift. The API supports any iOS platform that runs Swift, including Apple TV. CloudBees Feature Management’s documentation has details on how you can do a great deal more with flags, configuration settings, experiments, versions, and grouping users. You now have an understanding of feature flag management and how it can improve your iOS code.  You've seen how it can help you manage projects and eliminate unnecessary releases. Get to work!

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.