Sometime its beneficial to have different code running for each target, For example consider that are developing an app that communicates with a remote server, You may want to have two versions of your app each pointing to a different api endpoint environment. The development copy of your app will target www.dev-example.com/api which hosts the development copy of your server app, Production copy will point to www.prod-example.com/api which has the production copy of the server app.

In addition to above differences lets also require that the production app should have a red background while the development should have a green one.

We can achieve this conditional branching for the app at compile-time or at run-time.

Compile-time branching

By using preprocessors we can select which code is being compiled and which is not. Lets first create AppDevelopement and AppProduction targets that represent the Development and Production API endpoints, for each target we will define a different predecessor, in oder to set the preprocessor navigate to the project file, select the target then navigate to Build Settings.

Once in Build Settings search for Preprocessor Macros and inside both Debug and Release add a line with ProductionEnv for the production and DevelopmentEnv for development.

Now in your .m files you can write

NSString *api;
#ifdef ProductionEnv
  api = @"www.dev-example.com/api"
  self.view.backgroundColor = [UIColor redColor];
#endif

#ifdef DevelopmentEnv
  api = @"www.dev-example.com/api"
  self.view.backgroundColor = [UIColor greenColor];
#endif

Now when you run the app you will notice that the background color and the API endpoint will differ for each target.

The second way to declares custom preprocessors is by using Xcode config files .xcconfig, create an xcconfig file for each target and assign it, to assign the config file to a target; open Xcode project file, under Configurations expand Debug or Release and set the xcconfig file.

In order to fill settings in the xcconfig we need to navigate to the target Build Settings and under Preprocessors Macros select the Debug or Release row and copy it (by using command + c), then go back to the xcconfig and paste it, you should now have a file with:

For Production target:

GCC_PREPROCESSOR_DEFINITIONS = ProductionEnv $(inherited)

And for Development target:

GCC_PREPROCESSOR_DEFINITIONS = DevelopmentEnv $(inherited)

The $(inherited) tells Xcode to load the configuration settings that we filled into the target build settings in the project file.

The last way of setting the preprocessors by declaring them in the prefix header file, You first have to create different prefix header for each target, In order to set the prefix header for the target is to navigate to the build settings and fill the prefix path in the Prefix Header row.

In the prefix header for the Production target:

#define ProductionEnv

While in the prefix header for the Development target:

#define DevelopmentEnv

Run-time branching

The other way to run different code in each target(scheme) is by passing environment variables to the target. Environment variables are set in the Scheme and not in the targets.

In order to set different Environment variables you only have to duplicate the scheme and not the target.

Navigate to scheme settings, under Run select Environment Variables and set ProductionEnv=1 for Production and DevelopmentEnv=1 for the Development scheme,

In code then to read it:

//isProduction will return YES for Production scheme run
BOOL isProduction = [[NSProcessInfo processInfo] environment][@"ProductionEnv"];

//isDevelopment will return YES for Development scheme run
BOOL isDevelopment = [[NSProcessInfo processInfo] environment][@"DevelopmentEnv"];

The second way to conditionally branch code on runtime is using plist files, Create two different info.plist files, and set them to the targets, In the first info.plist add a boolean with key ProductionEnv, For the second info.plist add an item with key DevelopmentEnv

At runtime you can read the value from plist:

//isProduction will return YES for Production scheme run
BOOL isProduction = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"ProductionEnv"] boolValue];

//isDevelopment will return YES for Development scheme run
BOOL isDevelopment = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"DevelopmentEnv"] boolValue];