"Where is your #ifdef?" — The first question a C developer asks in Swift
\n\nIf you've ever transitioned from C, C++, or Objective-C to Swift, you've probably wondered: "How do I do conditional compilation here? Where is my beloved #ifdef?" This is one of the most popular questions on Stack Overflow — and for good reason. In the world of Swift, there is no preprocessor in the traditional sense, but that doesn't mean you can't flexibly manage builds for different platforms, configurations, or debug modes.
\n\nIn this article, we'll break down the best ways to replace #ifdef in Swift: from simple directives to advanced build schemes. You'll learn how to write code that works on both iOS and macOS, how to hide debug information in release builds, and how not to get confused with platform flags. Let's go!
\n\nWhy Swift doesn't have #ifdef (and that's a good thing)
\n\nThe C preprocessor is a powerful but dangerous tool. It works before the compiler, knows nothing about types or scopes. As a result, it's easy to get a #define that breaks everything around it. Swift developers chose a different path: they built conditional compilation directly into the language, making it safer and more transparent.
Instead of #ifdef, you use #if, #elseif, #else, and #endif — the syntax is similar, but it works at the Swift compiler level, not the preprocessor. Plus, they added several built-in flags, which we'll discuss now.
Approach 1: Classic #if directives — the closest relative to #ifdef
\n\nThe most direct way is to use #if with predefined conditions:
#if DEBUG\n print("This is a debug build")\n // Logging, additional checks can go here\n#else\n print("This is a release build")\n#endif\n\nHere is a list of built-in flags you can use:
\n\n- \n
DEBUG— automatically defined for Debug configuration (Xcode adds it automatically) \n os(iOS),os(macOS),os(tvOS),os(watchOS)— platform check \n arch(x86_64),arch(arm64),arch(i386),arch(arm)— processor architecture \n swift(>=5.0)— Swift language version \n canImport(UIKit)— module availability check \n targetEnvironment(simulator)— running on the simulator \n
Example of a combined check:
\n\n#if os(iOS) || os(tvOS)\n import UIKit\n typealias PlatformView = UIView\n#elseif os(macOS)\n import AppKit\n typealias PlatformView = NSView\n#else\n typealias PlatformView = Any\n#endif\n\nThis code creates a single alias for the view class that works on different platforms. Very convenient for cross-platform libraries!
\n\nApproach 2: Active Compilation Conditions — your own #ifdef
\n\nWhat if you need to add your own flag that isn't in the standard set? For example, BETA_FEATURES or PAYMENT_INTEGRATION. For this, Xcode has Active Compilation Conditions.
How to configure:
\n- \n
- Open your project in Xcode \n
- Select target → Build Settings → Swift Compiler — Custom Flags \n
- Find the Active Compilation Conditions field \n
- Add your flags separated by spaces, for example:
BETA PAYMENT_V2\n
Use it in code like this:
\n\n#if BETA\n print("Beta version launched")\n showBetaUI()\n#endif\n\n#if PAYMENT_V2\n let paymentProcessor = NewPaymentProcessor()\n#else\n let paymentProcessor = LegacyPaymentProcessor()\n#endif\n\nThis is a direct replacement for #ifdef from C/C++! You decide