Super Preprocessor Directives with Xcode 8

Update 3–25–2017

I actually don’t recommend this approach anymore but I shall keep the original contents of this article regardless. Instead, I recommend environment variables to enable latent functionality.( https://medium.com/@derrickho_28266/xcode-custom-environment-variables-681b5b8674ec#.16t6vgj5e )


Imagine you have a latent super power. You can’t use it because it is latent. What would you do if you could activate it with a special amulet? Would you do it?

In the world of code, you may want it to have latent abilities that can be unlocked as desired. An Xcode project is typically created to two configurations: debug and release. In the debug configuration we are given a Preprocessor Directive called DEBUG while release has no such thing.

This allows us to conditionally include or exclude code from compilation. Suppose you want to print something to the console but only when it is enabled.

This works because an Xcode project has DEBUG included as a preprocessor directive in the Build Settings. Most of the time, your Xcode project is only one project. However, in the advanced cases, your Xcode project may have Subprojects in which a framework is defined. Your Xcode project has a hardcoded value for DEBUG and your Subproject has a hard coded value for DEBUG. These values are limited in scope. The Subproject’s version only applies to its files while your projects version only applies to its own files excluding any of the Subprojects. This is because a Subproject isn’t really inside a project; that is an illusion. they are two separate entities that have their wires connected.

So if we were to define a Preprocessor Directive called ABC in our project’s build settings, it would only be available to our project but not our subproject.

So what if you wanted your project to be the captain of the ship and have his Preprocessor directive apply to all subprojects?

What we need to do is define the Preprocessor Directive in the project and have the subprojects get the value for it.

The tool we need to use is XCConfig.

Set-Up

  1. Open your Xcode project
  2. In the project navigator, click on the blue icon that represents your project.
  3. Under PROJECT select the blue icon that represents your project.
  4. Choose the info tab.
  5. Under configurations you should see Debug and Release. If it is a new project it will say “no configurations set”

We need to create a configuration file.

  1. Create a configuration file called Captain.xcconfig
  2. This is where we will create two Preprocessor Directives: One available for Objective-c and one for Swift.

I am making a variable called ABC_OBJC with the value ABC=1

I am making a variable called ABC_SWIFT with the value of ABC

3. For our Debug Configuration, click the triangle to reveal all the project targets. Lets set the configuration at the project level.

We need to add preprocessor directives to the build settings

Adding preprocessor directives for Objective-c

We entered $(ABC_OBJC) under Preprocessor Macros and $(ABC_SWIFT) under Active Compilation Conditions. The UI will look in Captain.xcconfig for those variables and realize they mean ABC=1 and ABC respectively. If we were to comment them out from Captain.xcconfig, then the UI will not find them and the Preprocessor directives will not exist.

Great!

Now we can use ABC to conditionally compile code by modifying Captain.xcconfig

Making a subproject Obey your project

Suppose I have a project called Captain and a subproject called Cadet. How can we get the Cadet to have the same ABC directive? When the Captain’s directive changes, the Cadet should acquire that change.

The secret lies with the XCConfig file’s ability to reference other XCConfig files.

  1. Create an XCConfig file called Debug.xcconfig
The file structure. Captain.xcconfig is one file level above Debug.xcconfig

2. Then in the Cadet, set the debug configuration to Debug.xcconfig

3. For the Cadet, enter $(ABC_OBJC) under Preprocessor Macros and $(ABC_SWIFT) under Active Compilation Conditions. It is the same step we did for Captain project but this time we do it to the Cadet Project.

The Cadet will look in Debug.xcconfig for $(ABC_OBJC) and $(ABC_SWIFT) only to find a reference to Captain.xcconfig so it goes to that file and finds the definitions it was looking for. Again, if it was unable to find the definitions to $(ABC_OBJC) and $(ABC_SWIFT), it would consider them whitespace or non-existent Preprocessor directives.

Now the Cadet will have the Preprocessor directive only if the Captain gives it out. In the above photo, the UI shows the value of $(ABC_SWIFT) as ABC. If all the plumbing was set up correctly, then Captain.xcconfig will dictate this value. Try commenting out ABC_SWIFT=ABC from Captain.xcconfig and save the file. The ABC should vanish from Active Compilation Conditions in both the Captain’s Build settings and the Cadets Build Settings.

To see a full example, click here.

You should now have a Super Preprocessor Directive. It is Super because its scope goes beyond a than a single project.

Applications

Why would anyone want to use this?

Suppose you have subproject named Car. You don’t want everyone to access your car’s ability to drive around. So when most people encounter your car is is a hunk of metal parked in the street. However, you are the Owner and you have they KEY to unlock the Car’s ability to drive. In this context The Owner has an xcconfig file and the Car references it. the xcconfig file contains the Key’s value.