Marcus Leon
Marcus Leon

Reputation: 56679

iOS: Simple way to manage REST end points

Our REST based application can be used for testing on multiple internal environments each with a different REST end point. Is there a simple way to set up environment level configuration within an iOS (Swift 3) app? I've seen a few approaches but they all seem pretty involved.

Upvotes: 2

Views: 2038

Answers (4)

Marcus Leon
Marcus Leon

Reputation: 56679

I ended up using https://github.com/theappbusiness/ConfigGenerator:

A command line tool to auto-generate configuration file code, for use in Xcode projects. The configen tool is used to auto-generate configuration code from a property list. It is intended to create the kind of configuration needed for external URLs or API keys used by your app. Currently supports both Swift and Objective-C code generation.

Upvotes: 0

Rajan Maheshwari
Rajan Maheshwari

Reputation: 14571

This is my approach of doing things when we have multiple end points. I used to make a ConfigurationManager class something like this

Swift 3.0 code

import Foundation
import UIKit

let kEnvironmentsPlist:NSString? = "Environments"
let kConfigurationKey:NSString? = "ActiveConfiguration"
let kAPIEndpointKey:NSString? = "APIEndPoint"
let kLoggingEnabledKey:NSString? = "LoggingEnabled"
let kAnalyticsTrackingEnabled:NSString? = "AnalyticsTrackingEnabled"

class ConfigurationManager:NSObject {

    var environment : NSDictionary?

    //Singleton Method

    static let sharedInstance: ConfigurationManager = {
        let instance = ConfigurationManager()
        // setup code
        return instance
    }()

    override init() {
        super.init()
        initialize()
    }

    // Private method

    func initialize ()   {

        var environments: NSDictionary?
        if let envsPlistPath = Bundle.main.path(forResource: "Environments", ofType: "plist") {
            environments = NSDictionary(contentsOfFile: envsPlistPath)
        }
        self.environment = environments!.object(forKey: currentConfiguration()) as? NSDictionary
        if self.environment == nil {
            assertionFailure(NSLocalizedString("Unable to load application configuration", comment: "Unable to load application configuration"))
        }
    }

    // CurrentConfiguration

    func currentConfiguration () -> String   {
        let configuration = Bundle.main.infoDictionary?[kConfigurationKey! as String] as? String
        return configuration!
    }

    // APIEndpoint

    func APIEndpoint () -> String  {
        let configuration = self.environment![kAPIEndpointKey!]
        return (configuration)! as! String
    }

    // isLoggingEnabled

    func isLoggingEnabled () -> Bool  {

        let configuration = self.environment![kLoggingEnabledKey!]
        return (configuration)! as! Bool
    }

    // isAnalyticsTrackingEnabled

    func isAnalyticsTrackingEnabled () -> String  {

        let configuration = self.environment![kAnalyticsTrackingEnabled!]
        return (configuration)! as! String
    }

    func applicationName()->String{
        let bundleDict = Bundle.main.infoDictionary! as NSDictionary
        return bundleDict.object(forKey: "CFBundleName") as! String
    }
}

In Project--> Info Add some new configurations as per your need.

enter image description here

I have added Staging and QA as extra endpoints.Generally I use to make Staging as Release config and QA as Debug. So it will look like:

enter image description here

Now go to Targets -> Build Settings and add a User Defined Setting

enter image description here

Give the name of the user defined like ACTIVE_CONFIGURATION.

enter image description here

Add a key named ActiveConfiguration in info.plist with a variable name as $(ACTIVE_CONFIGURATION) same as given in User Defined Settings with a $ in the beginning. We gave the name of key as ActiveConfiguration because we are using the same name in our ConfigurationManager.swift class for kConfigurationKey.

let kConfigurationKey:NSString? = "ActiveConfiguration"

You can define as per your naming convention.

It will look like:

enter image description here

Now in the ConfigurationManager class I am getting a path for Environments.plist file.

I will just make a Environments.plist file like this:

enter image description here

The actual description source of this file is

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Development</key>
    <dict>
        <key>APIEndPoint</key>
        <string>https://dev</string>
        <key>LoggingEnabled</key>
        <true/>
        <key>AnalyticsTrackingEnabled</key>
        <true/>
        <key>Flurry</key>
        <dict>
            <key>FlurryApplicationID</key>
            <string></string>
            <key>FlurryApplicationSecret</key>
            <string></string>
        </dict>
        <key>Facebook</key>
        <dict>
            <key>FacebookAppID</key>
            <string></string>
            <key>FacebookAppSecret</key>
            <string></string>
        </dict>
    </dict>
    <key>QA</key>
    <dict>
        <key>APIEndPoint</key>
        <string>https://qa</string>
        <key>LoggingEnabled</key>
        <true/>
        <key>AnalyticsTrackingEnabled</key>
        <true/>
        <key>Flurry</key>
        <dict>
            <key>FlurryApplicationID</key>
            <string></string>
            <key>FlurryApplicationSecret</key>
            <string></string>
        </dict>
        <key>Facebook</key>
        <dict>
            <key>FacebookAppID</key>
            <string></string>
            <key>FacebookAppSecret</key>
            <string></string>
        </dict>
    </dict>
    <key>Staging</key>
    <dict>
        <key>APIEndPoint</key>
        <string>https://staging</string>
        <key>LoggingEnabled</key>
        <false/>
        <key>AnalyticsTrackingEnabled</key>
        <true/>
        <key>Flurry</key>
        <dict>
            <key>FlurryApplicationID</key>
            <string></string>
            <key>FlurryApplicationSecret</key>
            <string></string>
        </dict>
        <key>Facebook</key>
        <dict>
            <key>FacebookAppID</key>
            <string>840474532726958</string>
            <key>FacebookAppSecret</key>
            <string></string>
        </dict>
    </dict>
    <key>Production</key>
    <dict>
        <key>APIEndPoint</key>
        <string>https://production</string>
        <key>LoggingEnabled</key>
        <true/>
        <key>AnalyticsTrackingEnabled</key>
        <true/>
        <key>Flurry</key>
        <dict>
            <key>FlurryApplicationID</key>
            <string></string>
            <key>FlurryApplicationSecret</key>
            <string></string>
        </dict>
        <key>Facebook</key>
        <dict>
            <key>FacebookAppID</key>
            <string></string>
            <key>FacebookAppSecret</key>
            <string></string>
        </dict>
    </dict>
</dict>
</plist>

We are now good to go. Now you have to just call

ConfigurationManager.sharedInstance.APIEndpoint()

for your respective end points.

Now you just have to change the schemes from Edit Schemes and you are done and change the Build Configuration in info.

enter image description here

enter image description here

This not only manages API End Points but also other things like whether to enable analytics or tracking for the respective end point or different ids of Facebook for different end points.

Upvotes: 2

dalton_c
dalton_c

Reputation: 7171

As Zac Kwan suggested, you can use different schemes to accomplish this, but you don't necessarily have to create a different configuration as well. Each scheme can specify unique environment variables. Then, access them from Swift:

let prodURL = "http://api.com"
let baseURL = ProcessInfo.processInfo.environment["BASE_URL"] ?? prodURL

Upvotes: 1

Zac Kwan
Zac Kwan

Reputation: 5747

I found that creating different Scheme and Configuration for your project works best. My setup is as follow:

I usually have 3 different scheme, MyApp-dev, MyApp-staging and MyApp.

Each of the scheme i created User-Defined-Attribute to have different string appending to my Bundle Display Name. So it can concurrently appear on my iOS device as MyApp-d, MyApp-s and MyApp. Each also have its own Bundle ID Then I create custom flags for each of them.

So in my Routes.swift files i have something like this at the top:

#if PRODUCTION
    static let hostName = "http://production.com/api/v1/"
#elseif STAGING
    static let hostName = "http://staging.com/api/v1/"
#else
    static let hostName = "http://development.com/api/v1/"
#endif

There is quite a few ways in how to update different hostname. But ultimately creating different Scheme and Configuration is always the first step.

Here is a few links that might help you get started:

https://medium.com/@danielgalasko/change-your-api-endpoint-environment-using-xcode-configurations-in-swift-c1ad2722200e#.o6nhic3pf

http://limlab.io/swift/2016/02/22/xcode-working-with-multiple-environments.html

Upvotes: 0

Related Questions