Active Compilation Conditions는 빌드 단계에서 컴파일러에 전달되는 플래그로, 최종 바이너리 파일에서 특정 코드 블록을 포함하거나 제외할 수 있게 합니다. Apple Developer Documentation (2026)에 따르면, Swift는 OTHER_SWIFT_FLAGS 키와 #if 지시문을 통해 Active Compilation Conditions를 지원합니다. Active Compilation Conditions는 개발자가 런타임 검사 없이 디버깅, 테스트 및 프로덕션용으로 다른 버전의 코드를 빌드할 수 있는 기능을 제공합니다.
핵심 포인트
Active Compilation Conditions는 빌드 시 활성 프리프로세서 지시문 세트를 결정하는 컴파일 플래그입니다. 런타임 플래그(if (isDebug) 확인)와 달리, 컴파일 조건은 비활성 코드를 바이너리 파일에서 물리적으로 제외하여 성능 향상과 애플리케이션 크기 감소를 제공합니다.
이 메커니즘은 프리프로세서 또는 초기 컴파일 단계 수준에서 작동합니다. 컴파일러는 활성 이름 목록을 받고 #if NAME 지시문을 만나면 NAME이 해당 목록에 있는지 확인합니다. 이름이 없으면 블록 내부의 코드는 무시되고 컴파일되지 않습니다.
Swift.org Blog (2025)에 따르면, 런타임 플래그 대신 Active Compilation Conditions를 사용하면 광범위한 로깅 및 디버깅 도구를 갖춘 프로젝트에서 릴리스 바이너리 크기가 평균 12-18% 감소합니다. 이는 설치 파일 크기 제한이 있는 모바일 애플리케이션에서 특히 중요합니다.
C/C++ 프리프로세서 수준의 조건부 컴파일과의 주요 차이점은 Swift와 Kotlin의 Active Compilation Conditions가 텍스트 대체 수준이 아닌 컴파일러의 AST(추상 구문 트리) 수준에서 작동한다는 것입니다. 이는 더 안전하고 예측 가능하게 만듭니다. 비활성 #if 분기의 구문 오류는 구문 분석 중에 감지되며 런타임에 나타나지 않습니다.
또 다른 중요한 차이점은 Swift에서 조건 #if os(iOS) || os(macOS)는 컴파일 시간에 확인되며 프리프로세서 매크로가 아닌 플랫폼 이름으로 작동한다는 것입니다. 이는 C/C++ 프리프로세서에서 가능한 #define을 통한 잘못된 텍스트 삽입과 관련된 버그 클래스 전체를 제거합니다. Swift 컴파일러는 대체된 텍스트가 아닌 AST를 보므로 조건부 컴파일 디버깅이 훨씬 쉬워집니다.
Swift는 논리 연산자 &&, || 및 !와 결합된 플래그 이름 목록을 허용하는 #if 지시문을 통해 Active Compilation Conditions를 지원합니다. 컴파일러는 조건이 true인 경우에만 #if ... #endif 내부의 코드를 포함합니다.
Swift는 여러 내장 조건을 제공합니다: DEBUG(디버그 빌드에서 자동 활성화), swift(>=5.0)(컴파일러 버전 확인), canImport(UIKit)(모듈 가용성 확인) 및 targetEnvironment(simulator)(환경 확인). 이러한 조건은 추가 설정이 필요하지 않습니다.
// Swift 내장 조건
#if DEBUG
print("디버그 빌드 — 로깅 활성")
#endif
#if canImport(UIKit)
import UIKit
let screen = UIScreen.main.bounds
#elseif canImport(AppKit)
import AppKit
let screen = NSScreen.main?.frame
#endif
개발자는 Xcode의 Build Setting OTHER_SWIFT_FLAGS를 통해 자신의 플래그를 추가할 수 있습니다. 플래그는 접두사 -D로 지정됩니다. 예: -DBETA 또는 -DANALYTICS_ENABLED. 다른 구성(Debug, Release, Staging)에 대해 다른 플래그 세트를 설정할 수 있습니다.
// 사용자 정의 BETA 플래그 처리
#if BETA
let apiEndpoint = "https://beta.api.com"
let isLoggingEnabled = true
#else
let apiEndpoint = "https://api.com"
let isLoggingEnabled = false
#endif
func trackEvent(_ name: String) {
#if ANALYTICS_ENABLED
Analytics.log(name)
#endif
}
Swift는 플랫폼 조건을 지원합니다: os(iOS), os(macOS), os(tvOS), os(watchOS), os(Linux), os(Windows). 이러한 조건은 대상 빌드 플랫폼을 확인하고 플랫폼별 블록으로 여러 Apple 플랫폼에서 공유 코드를 작성할 수 있게 합니다.
import Foundation
func getDeviceName() -> String {
#if os(iOS)
return UIDevice.current.name
#elseif os(macOS)
return Host.current.name ?? "Unknown"
#else
return "Other platform"
#endif
}
Android 생태계에서 Active Compilation Conditions는 BuildConfig 시스템, productFlavors 및 build.gradle.kts의 플래그를 통해 구현됩니다. Kotlin은 언어 수준에서 #if 지시문의 직접적인 동등물이 없지만 대체 메커니즘을 제공합니다.
가장 일반적인 방법은 각 플래그에 buildConfigField를 추가하는 것입니다: buildConfigField("boolean", "BETA", "true"). 이러한 필드는 각 Build Variant에 대해 별도로 BuildConfig 클래스에 생성됩니다. DEBUG 필드는 이미 내장되어 있으며 디버그 빌드에서 자동으로 true입니다.
// build.gradle.kts
android {
buildTypes {
debug {
buildConfigField("boolean", "BETA", "true")
}
release {
buildConfigField("boolean", "BETA", "false")
}
}
}
// Kotlin 코드에서 사용
if (BuildConfig.BETA) {
enableBetaFeatures()
}
Gradle은 각 flavor에 대해 별도의 sourceSets 디렉토리를 만들 수 있습니다. 예: src/demo/ 및 src/full/. 다른 sourceSets의 동일한 이름을 가진 클래스는 해당 flavor를 빌드할 때 서로를 대체합니다. 이는 전체 클래스를 재정의할 수 있기 때문에 플래그보다 더 강력한 메커니즘입니다.
// src/demo/java/com/example/Config.kt
object Config {
const val API_URL = "http://demo.api.com"
const val IS_BETA = true
}
// src/full/java/com/example/Config.kt
object Config {
const val API_URL = "https://full.api.com"
const val IS_BETA = false
}
Kotlin Multiplatform(KMP)의 경우 expect/actual 지시문을 사용할 수 있으며, 공통 코드에서 예상 선언을 선언하고 플랫폼별 구현을 제공할 수 있습니다. 이는 효과 면에서 Active Compilation Conditions와 유사한 컴파일러 수준 메커니즘입니다 — 부적합한 플랫폼의 경우 비활성 코드가 컴파일되지 않습니다.
Active Compilation Conditions는 네 가지 주요 시나리오에서 사용됩니다: 디버깅(로그, 검사기), A/B 테스트(기능 플래그), 플랫폼 적응(iOS/macOS 공유 코드) 및 라이선싱(무료/유료 버전).
가장 일반적인 시나리오는 조건부 로깅입니다. 디버그 빌드에서는 모든 로그가 콘솔에 기록되고, 릴리스 빌드에서는 아무것도 기록되지 않습니다. #if DEBUG 또는 BuildConfig.DEBUG를 사용하면 릴리스 바이너리에 인라인된 것조차 로거 호출이 하나도 포함되지 않도록 보장합니다.
func logRequest(_ url: URL, _ statusCode: Int) {
#if DEBUG
Logger.debug("Request to \(url.absoluteString) returned \(statusCode)")
#endif
}
새 기능이 아직 프로덕션에 준비되지 않았지만 코드에 이미 존재하는 경우 컴파일 플래그 뒤에 숨길 수 있습니다. 런타임 기능 플래그와 달리, 컴파일 플래그는 검사로 애플리케이션에 부담을 주지 않으며 사용자가 활성화할 수 없습니다.
Active Compilation Conditions는 절제하여 사용해야 합니다. 과도한 수의 플래그는 코드를 이해하기 어렵게 만듭니다. 개발자는 특정 시점에 어떤 분기가 컴파일될지 확신할 수 없습니다. 각 플래그는 README 또는 전용 CONFIG.md 파일에 문서화하는 것이 좋습니다.
분산 팀이 있는 대규모 프로젝트에서는 CI에서 자동 플래그 검증을 구현하는 것이 유용합니다. 각 풀 리퀘스트는 Active Compilation Conditions의 가능한 모든 조합으로 빌드를 통과해야 합니다. 이는 비활성 플래그 아래의 코드가 리팩토링으로 인해 손상되지 않았고, 릴리스까지 조건부 컴파일 분기가 테스트되지 않은 상태로 남지 않도록 보장합니다. xcresulttool(iOS용) 및 Gradle Build Scan(Android용)과 같은 도구가 이 프로세스를 자동화하는 데 도움이 됩니다.
자주 묻는 질문
#if DEBUG는 컴파일 지시문입니다. DEBUG가 활성화되지 않으면 블록 내부의 코드가 바이너리에 포함되지 않습니다. if (isDebug)는 런타임 검사입니다. 코드는 항상 컴파일되고 조건은 런타임에 확인됩니다. #if는 릴리스 빌드에 흔적을 남기지 않습니다.
프로젝트의 Build Settings에서 Other Swift Flags(OTHER_SWIFT_FLAGS)를 찾고 플래그와 함께 새 줄을 추가합니다: -DMY_FLAG. 플래그는 #if MY_FLAG 지시문에서 볼 수 있습니다. Debug 및 Release 구성에 대해 다른 플래그를 설정할 수 있습니다.
Kotlin/JVM에는 직접적인 동등물이 없습니다. 대신 BuildConfig 필드가 사용됩니다(런타임 검사이지만 ProGuard가 사용되지 않는 코드를 제거할 수 있음). Kotlin Multiplatform에서는 선언 수준에서 expect/actual 지시문이 있습니다.
네, Swift는 논리 연산자를 지원합니다: #if DEBUG && BETA, #if os(iOS) || os(tvOS), #if !RELEASE. 복잡한 논리를 위해 조건을 괄호로 그룹화할 수 있습니다. AND와 OR는 표준 숏서킷 규칙으로 작동합니다.
SwiftUI 미리보기는 기본 대상과 다른 플래그를 사용하여 별도 프로세스에서 빌드됩니다. DEBUG가 활성화되지 않을 수 있습니다. 해결책: 미리보기 코드에는 targetEnvironment(simulator)를 사용하거나 조건부 로직을 별도 메서드로 추출하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.