Skip to content

Instantly share code, notes, and snippets.

@besi
Created May 24, 2012 14:56
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save besi/2782045 to your computer and use it in GitHub Desktop.
Save besi/2782045 to your computer and use it in GitHub Desktop.
Environment

Introduction

This is a gist which should provide a modular way of adding Environment settings to your iOS or Mac application like described in the excellent article from the guys at CarbonFive.

This Framework maps different Xcode configurations, Debug and Release being the default, to different sets of settings or Environments like development, staging1, staging2, production and so on.

All the different settings are stored in the Environments.plist file as dictionaries with the given environment as the key.

Check out the gist

Install the Gist

Execute the following script to install (Please change the variables accordingly):

# Only customization
export YOUR_TARGET=ADD_NAME_HERE

# Folders
export VENDOR_FOLDER=$YOUR_TARGET/Vendor/Environment
export PERSONAL_FOLDER=$YOUR_TARGET/Environment

# Clone the file		
git clone git@github.com:2782045.git $VENDOR_FOLDER

# Add the git module
echo "
[submodule \"Environment\"]
  path = ${VENDOR_FOLDER}
  url = git://gist.github.com/2782045.git
" >> .gitmodules

# Create the initial files
mkdir -p $PERSONAL_FOLDER
cp $VENDOR_FOLDER/Environments.template.plist $PERSONAL_FOLDER/Environments.plist
echo "" > $PERSONAL_FOLDER/Environment+$YOUR_TARGET.h
echo "" > $PERSONAL_FOLDER/Environment+$YOUR_TARGET.m

echo "Don't forget to add the files to Xcode
$PERSONAL_FOLDER
$VENDOR_FOLDER	
"

Setup the project

  1. In your project settings on the Info tab add any new categories such as Preview

  2. Add the .h, .m and .plist file in the EnvironmentGist directory to Xcode

  3. Add a new key to your app's -Info.plist file:

     <key>Environment</key>
     <string>${CONFIGURATION}</string>
    
  4. Update the Environments.plist file with your desired environments which should be top-level dictionaries. They need to correspond with the environments, that you have configured in your project (Step 1).

  5. Add a category for Environment.h which contains the properties that you need:

     #import <Foundation/Foundation.h>
     #import "Environment.h"
    
     @interface Environment(YourApp)
    
     ENVIRONMENT_PROPERTY(myApiURL)
    
     @end
    

    The implementation

     #import "Environment+YourApp.h"
    
     @implementation Environment(YourApp)
    
     ENVIRONMENT_GETTER(myApiURL)
     
     @end
    
  6. Update the projects .pch file and add the created category:

     #import "Environment+YourApp.h"
    
  7. Add different bundle identifiers for the different configurations:

    • For your target in Build Settings add a User Build Setting ENVIRONMENT_SUFFIX and set it to ${CONFIGURATION} for all but the Release configuration.
    • Change the Bundle Identifier in your Info.plist file to `com.domain.yourapp${ENVIRONMENT_SUFFIX}.

    This will result in com.domain.yourapp-Debug for example. Please note that all changes require a Clean to take effect.

  8. And your icon files CFBundleIcons.

     <?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>CFBundlePrimaryIcon</key>
     	<dict>
     		<key>CFBundleIconFiles</key>
     		<array>
     			<string>Icon${ICON_SUFFIX}.png</string>
     			<string>Icon${ICON_SUFFIX}-72.png</string>
     			<string>Icon${ICON_SUFFIX}@2x.png</string>
     		</array>
     		<key>UIPrerenderedIcon</key>
     		<true/>
     	</dict>
     </dict>
     </plist>
    
  9. Optionally, also add the same kind of suffix to the CFBundleDisplayName

Usage

When reading a value use the following syntax to return a given value from the Environments.

[Environment sharedInstance].myApiURL

Please note that all newly defined properties should be added to your category. However, values can also be accessed like so:

[[Environment sharedInstance] valueForKey:@"myKey"]

Fallback default-values

It is possible to define an Environment as a master by adding the key _isMaster = YES as a subkey in the Environments.plist file.

Example:

Assume that there are two Configurations defined: Release and Preview. The Release Environment has the _isMaster key set.

Even if the current Envrionment is set to Preview, any values that don't exist there will be looked up in the Release Environment.

Ideas

  • The Environment.plist ends up in the App bundle and is not encrypted

  • Nested substitutions of values would be cool:

      baseURL = http://domain.com/api
      contactsURL = {baseURL}/contacts
    
//
// Environment.h
//
#import <Foundation/Foundation.h>
// Convenience Macros
#define ENVIRONMENT_PROPERTY(key) @property (readonly) NSString *key;
#define ENVIRONMENT_PROPERTY_BOOL(key) @property (readonly) BOOL key;
#define ENVIRONMENT_GETTER(key) -(NSString*)key{return[self valueForKey:@"" #key];}
#define ENVIRONMENT_GETTER_INTEGER(key) -(NSUInteger )key{return [((NSNumber *)[self valueForKey:@"" #key]) intValue];}
#define ENVIRONMENT_GETTER_BOOL(key) \
-(BOOL)key{\
NSNumber *value = [self valueForKey:@"" #key];\
return value.boolValue; \
}
/**
This class is a facade to different properties stored within Environments.plist. Depending on the build configuration a different set of the same properties is used.
*/
@interface Environment : NSObject
+ (Environment *)sharedInstance;
+ (NSString *)currentEnvironment;
@end
//
// Environment.m
// SwisscomTV
//
// Created by Beat Besmer on 30.01.12.
// Copyright (c) 2012 Beat Besmer. All rights reserved.
//
// See gist:
//
#import "Environment.h"
@interface Environment()
@property (strong) NSDictionary *environmentDict;
@end
@implementation Environment
@synthesize environmentDict = _environmentDict;
static Environment *sharedInstance;
#pragma mark - Initialize
- (void)initializeSharedInstance
{
NSBundle* bundle = [NSBundle mainBundle];
NSString* envsPListPath = [bundle pathForResource:@"Environments" ofType:@"plist"];
NSDictionary* environments = [[NSDictionary alloc] initWithContentsOfFile:envsPListPath];
self.environmentDict = [environments objectForKey:[Environment currentEnvironment]];
}
#pragma mark - Lifecycle Methods
+ (Environment *)sharedInstance
{
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[Environment alloc] init];
[sharedInstance initializeSharedInstance];
}
return sharedInstance;
}
}
+(NSString *)currentEnvironment{
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"Environment"];
}
-(id)valueForKey:(NSString *)key{
return [self.environmentDict valueForKey:key];
}
@end
<?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>Debug</key>
<dict>
<key>apiBaseURL</key>
<string>http://localhost:8080</string>
</dict>
<key>Preview</key>
<dict>
<key>apiBaseURL</key>
<string>http://dev.server.com</string>
</dict>
<key>Release</key>
<dict>
<key>_isMaster</key>
<true/>
<key>apiBaseURL</key>
<string>http://server.com</string>
</dict>
</dict>
</plist>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment