Swift 언어에서 #ifdef 교체


735

C / C ++ / Objective C에서는 컴파일러 전처리기를 사용하여 매크로를 정의 할 수 있습니다. 또한 컴파일러 전처리기를 사용하여 일부 코드 부분을 포함 / 제외 할 수 있습니다.

#ifdef DEBUG
    // Debug-only code
#endif

Swift에도 비슷한 솔루션이 있습니까?


1
아이디어로서 이것을 obj-c 브리징 헤더에 넣을 수 있습니다.
— Matej

42
선택할 수있는 몇 가지가 있으므로 답변을 수여해야합니다.이 질문은 많은 투표를 얻었습니다.
— David H

답변:


1069

그렇습니다 당신은 그것을 할 수 있습니다.

Swift에서는 Apple 문서에 따라 여전히 "# if / # else / # endif"전 처리기 매크로를 사용할 수 있습니다 (더 제한적 임) . 예를 들면 다음과 같습니다.

#if DEBUG
    let a = 2
#else
    let a = 3
#endif

이제 "DEBUG"기호를 다른 곳에 설정해야합니다. "Swift Compiler-Custom Flags"섹션, "Other Swift Flags"라인에서 설정하십시오. -D DEBUG항목 과 함께 DEBUG 기호를 추가합니다 .

일반적으로 디버그 또는 릴리스시 다른 값을 설정할 수 있습니다.

실제 코드로 테스트했으며 작동합니다. 운동장에서는 인식되지 않는 것 같습니다.

내 원래 게시물을 여기에서 읽을 수 있습니다 .


중요 사항 : -DDEBUG=1 작동하지 않습니다. 만 -D DEBUG작동합니다. 컴파일러가 특정 값을 가진 플래그를 무시하는 것 같습니다.


41
플래그의 존재 여부 만 확인할 수 있지만 특정 값은 확인할 수는 없지만 정답입니다.
— Charles Harley

19
추가 참고 : 추가의 위에 -D DEBUG상술 한 바와 같이, 당신은 또한 정의 할 필요가 DEBUG=1에서 Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
— Matthew Quiros

38
-DDEBUG이 답변 의 형식을 stackoverflow.com/a/24112024/747369 로 변경하기 전까지는이 작업을 수행 할 수 없었습니다 .
— Kramer

11
@MattQuiros Objective-C 코드에서 사용하지 않으려는 경우 에 추가 DEBUG=1할 필요 Preprocessor Macros가 없습니다.
— derpoliuk

7
@Daniel 표준 부울 연산자를 사용할 수 있습니다 (예 :`#if!
— DEBUG`

353

Apple Docs에 명시된 바와 같이

Swift 컴파일러에는 전처리 기가 포함되어 있지 않습니다. 대신 컴파일 타임 속성, 빌드 구성 및 언어 기능을 활용하여 동일한 기능을 수행합니다. 이러한 이유로 전 처리기 지시문은 Swift에서 가져 오지 않습니다.

사용자 정의 빌드 구성을 사용하여 원하는 것을 달성했습니다.

  1. 프로젝트로 이동 / 대상 선택 / 빌드 설정 / 사용자 정의 플래그 검색
  2. 선택한 대상의 경우 디버그 및 릴리스 모두에 -D 접두사 (공백없이)를 사용하여 사용자 정의 플래그를 설정하십시오.
  3. 가지고있는 모든 대상에 대해 위의 단계를 수행하십시오.

대상을 확인하는 방법은 다음과 같습니다.

#if BANANA
    print("We have a banana")
#elseif MELONA
    print("Melona")
#else
    print("Kiwi")
#endif

여기에 이미지 설명을 입력하십시오

Swift 2.2를 사용하여 테스트


4
1. 공백 작업도 가능하므로 디버그에만 플래그를 설정해야합니까?
— c0ming

3
@ c0ming 필요에 따라 다르지만 릴리스가 아닌 디버그 모드에서만 발생하는 것을 원하면 릴리스에서 -DDEBUG를 제거해야합니다.
— cdf1982

1
-DLOCAL내 사용자 지정 플래그를 설정하면 내 섹션 #if LOCAl #else #endif에 해당 #else합니다. 원래 대상을 복제 AppTarget하고 이름을 바꾸고 AppTargetLocal맞춤 플래그를 설정했습니다.
— Perwyl Liu

3
@Andrej XCTest가 커스텀 플래그를 인식하도록 만드는 방법을 알고 있습니까? #if LOCAL 시뮬레이터로 실행할 때 의도 한 결과에 도달하고 #else 테스트 중에 빠진다는 것을 알고 있습니다 . #if LOCAL테스트 중에도 빠지기를 원합니다 .
— Perwyl Liu

3
이것이 정답입니다. 현재 허용되는 답변은 Objective-C에만 적용되므로 Swift에는 올바르지 않습니다.
— miken.mkndev

171

많은 상황에서 실제로 조건부 컴파일 이 필요하지 않습니다 . 켜고 끌 수있는 조건부 동작 만 있으면 됩니다. 이를 위해 환경 변수를 사용할 수 있습니다. 이것은 실제로 다시 컴파일 할 필요가 없다는 큰 이점이 있습니다.

체계 편집기에서 환경 변수를 설정하고 쉽게 켜거나 끌 수 있습니다.

여기에 이미지 설명을 입력하십시오

NSProcessInfo를 사용하여 환경 변수를 검색 할 수 있습니다.

    let dic = NSProcessInfo.processInfo().environment
    if dic["TRIPLE"] != nil {
        // ... do secret stuff here ...
    }

실제 예는 다음과 같습니다. 내 앱은 시뮬레이터에없는 음악 라이브러리를 사용하기 때문에 기기에서만 실행됩니다. 그렇다면 내가 소유하지 않은 장치의 시뮬레이터에서 스크린 샷을 찍는 방법은 무엇입니까? 스크린 샷이 없으면 AppStore에 제출할 수 없습니다.

가짜 데이터 와 다른 처리 방법이 필요 합니다 . 두 가지 환경 변수가 있습니다. 하나는 켜면 앱이 기기에서 실행되는 동안 실제 데이터에서 가짜 데이터를 생성하도록 지시합니다. 다른 하나는 스위치를 켤 때 시뮬레이터에서 실행되는 동안 누락 된 음악 라이브러리가 아닌 가짜 데이터를 사용합니다. Scheme 편집기의 환경 변수 체크 박스 덕분에 각 특수 모드를 쉽게 켜고 끌 수 있습니다. 그리고 아카이빙에는 환경 변수가 없기 때문에 실수로 App Store 빌드에서 사용할 수 없다는 것이 보너스입니다.


어떤 이유로 두 번째 앱 실행시 내 환경 변수가 nil로 반환되었습니다.
— Eugene

60
조심 : 환경 변수는 모든 빌드 구성에 대해 설정되며 개별 구성에 대해 설정할 수 없습니다. 따라서 릴리스인지 디버그 빌드인지에 따라 동작을 변경해야하는 경우에는 실행 가능한 솔루션 이 아닙니다 .
— Eric

5
@Eric Agreed이지만 모든 구성표 조치에 대해 설정되지 않았습니다. 따라서 빌드 앤 런에서 하나의 작업을 수행하고 아카이브에서 다른 작업을 수행 할 수 있습니다. 또는 실제 구성과 같은 여러 가지 구성표를 사용할 수도 있습니다. 또한 대답에서 말했듯이 구성표에서 환경 변수를 쉽게 켜고 끌 수 있습니다.
— matt

10
환경 변수는 아카이브 모드에서 작동하지 않습니다. XCode에서 앱을 시작할 때만 적용됩니다. 기기에서이 기기에 액세스하려고하면 앱이 중단됩니다. 어려운 길을 찾았습니다.
— iupchris10

2
@ iupchris10 "아카이브에는 환경 변수가 없습니다"는 위의 대답의 마지막 단어입니다. 내가 대답 한대로 좋은 것 입니다. 그것은이다 점 .
— matt

159

의 주요 변화 ifdef교체의 엑스 코드 (8) 즉, 사용 내놓았다 액티브 컴파일 조건 .

Xcode 8 릴리스 노트의 빌드 및 링크 를 참조하십시오 .

새로운 빌드 설정

새로운 설정 : SWIFT_ACTIVE_COMPILATION_CONDITIONS

“Active Compilation Conditions” is a new build setting for passing conditional compilation flags to the Swift compiler.

이전에는 OTHER_SWIFT_FLAGS에서 조건부 컴파일 플래그를 선언해야했는데 설정 앞에“-D”를 붙여야합니다. 예를 들어, MYFLAG 값으로 조건부 컴파일하려면 다음을 수행하십시오.

#if MYFLAG1
    // stuff 1
#elseif MYFLAG2
    // stuff 2
#else
    // stuff 3
#endif

설정에 추가 할 값 -DMYFLAG

이제 MYFLAG 값만 새 설정에 전달하면됩니다. 모든 조건부 컴파일 값을 이동할 시간입니다!

Xcode 8의 Swift Build Settings 기능에 대한 자세한 내용은 아래 링크를 참조하십시오 .


빌드 타임에 설정된 활성 컴파일 조건을 비활성화 할 수 있습니까? 테스트를 위해 디버그 구성을 빌드 할 때 DEBUG 조건을 비활성화해야합니다.
— Jonny

1
@Jonny 내가 찾은 유일한 방법은 프로젝트의 세 번째 빌드 구성을 만드는 것입니다. 프로젝트> 정보 탭> 구성에서 '+'를 누른 다음 디버그를 복제하십시오. 그런 다음이 구성에 대한 활성 컴파일 조건을 사용자 정의 할 수 있습니다. 새로운 빌드 구성을 사용하도록 대상> 테스트 체계를 편집하는 것을 잊지 마십시오!
— matthias

1
이것은 정답이어야합니다. Swift 4.x를 사용하여 xCode 9에서 저에게 효과적이었습니다!
— shokaveli

1
BTW, Xcode 9.3 Swift 4.1 DEBUG는 이미 활성 컴파일 조건에 있으며 DEBUG 구성을 확인하기 위해 추가 할 필요는 없습니다. #if DEBUG와 #endif 만 있으면됩니다.
— Denis Kutlubaev

나는 이것이 주제 외적이며 나쁜 짓이라고 생각합니다. 활성 컴파일 조건을 비활성화하지 않습니다. 테스트를 위해 새롭고 다른 구성이 필요합니다. "Debug"태그가 없습니다. 계획에 대해 배우십시오.
— Motti Shneor

93

Swift 4.1부터는 디버그 또는 릴리스 구성으로 코드를 빌드했는지 여부 만 확인하면 내장 기능을 사용할 수 있습니다.

  • _isDebugAssertConfiguration()(최적화가로 설정된 경우 true -Onone)
  • _isReleaseAssertConfiguration()(최적화가로 설정된 경우 true -O) (Swift 3+에서는 사용할 수 없음)
  • _isFastAssertConfiguration()(최적화가로 설정된 경우 true -Ounchecked)

예 :

func obtain() -> AbstractThing {
    if _isDebugAssertConfiguration() {
        return DecoratedThingWithDebugInformation(Thing())
    } else {
        return Thing()
    }
}

전 처리기 매크로와 비교할 때

  • ✓ -D DEBUG사용하기 위해 커스텀 플래그를 정의 할 필요가 없습니다.
  • ~ 실제로 Xcode 빌드 구성이 아닌 최적화 설정 측면에서 정의됩니다.
  • document 문서화되지 않음-모든 업데이트에서 함수를 제거 할 수 있음을 의미합니다 (그러나 옵티마이 저가이를 상수로 변환하므로 AppStore 안전해야 함).

  • if if / else를 사용하면 항상 "실행되지 않습니다"경고가 발생합니다.


1
이러한 내장 함수는 컴파일 타임이나 런타임에 평가됩니까?
— ma11hew28

@MattDiPasquale 최적화 시간. 릴리스 모드에서 if _isDebugAssertConfiguration()평가되며 디버그 모드입니다. if falseif true
— kennytm

2
그래도 릴리스에서 디버그 전용 변수를 선택 해제하기 위해 이러한 함수를 사용할 수 없습니다.
— Franklin Yu

3
이 기능들은 어딘가에 기록되어 있습니까?
— Tom Harrington

7
Swift 3.0 및 XCode 8부터는 이러한 기능이 유효하지 않습니다.
— CodeBender

86

Xcode 8 이상

빌드 설정 / Swift 컴파일러-사용자 정의 플래그 에서 활성 컴파일 조건 설정을 사용하십시오 .

  • 조건부 컴파일 플래그를 Swift 컴파일러에 전달하기위한 새로운 빌드 설정입니다.
  • 이런 간단한 추가 플래그 : ALPHA, BETA등

그런 다음 다음 과 같은 컴파일 조건으로 확인하십시오 .

#if ALPHA
    //
#elseif BETA
    //
#else
    //
#endif

팁 : #if !ALPHA등 을 사용할 수도 있습니다 .


77

스위프트 전처리 기가 없습니다. (무엇이든 임의의 코드 대체는 유형 및 메모리 안전성을 손상시킵니다.)

Swift에는 빌드 타임 구성 옵션이 포함되어 있으므로 특정 플랫폼 또는 빌드 스타일에 대한 코드를 조건부로 포함하거나 -D컴파일러 인수로 정의한 플래그에 응답 할 수 있습니다 . 그러나 C와 달리 조건부로 컴파일 된 코드 섹션은 구문 상 완전해야합니다. Cocoa 및 Objective-C와 함께 Swift 사용에 이에 대한 섹션이 있습니다.

예를 들면 다음과 같습니다.

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif

34
"무엇이든, 임의의 코드 대체는 유형 및 메모리 안전성을 손상시킵니다." 전처리 기가 컴파일러보다 먼저 작동하지 않습니까 (따라서 이름)? 따라서 이러한 모든 검사가 여전히 수행 될 수 있습니다.
— Thilo

10
@Thilo 나는 그것이 깨지는 것이 IDE 지원이라고 생각한다
— Aleksandr Dubinsky

1
@rickster가 얻는 것은 C 전 처리기 매크로가 유형을 이해하지 못하고 그 존재로 인해 Swift의 유형 요구 사항을 위반한다는 것입니다. C에서 매크로가 작동하는 이유는 C가 암시 적 유형 변환을 허용하기 때문입니다. 즉, 허용 INT_CONST되는 모든 위치에 넣을 수 있습니다 float. 스위프트는 이것을 허용하지 않을 것입니다. 또한 var floatVal = INT_CONST필연적으로 할 수 있다면 컴파일러가 나중에 예상 Int하지만 어딘가에 Float(유형 floatVal은으로 추론됩니다 Int) 어딘가에 고장이 발생합니다 . 10 캐스팅 후 매크로를 제거하기 위해 더 깨끗합니다 ...
— Ephemera

나는 이것을 사용하려고 노력하고 있지만 작동하지 않는 것 같습니다. iOS 빌드에서 여전히 Mac 코드를 컴파일하고 있습니다. 조정해야 할 다른 설정 화면이 있습니까?
— Maury Markowitz

1
@Thilo는 정확합니다. 전처리 기는 어떤 유형이나 메모리 안전을 깨뜨리지 않습니다.
— tcurdt

50

Xcode 8의 2 센트

a) -D접두사를 사용하는 사용자 정의 플래그 는 정상적으로 작동하지만 ...

b) 더 간단한 사용법 :

Xcode 8에는 디버그 및 릴리스를위한 두 개의 행이 이미있는 "활성 컴파일 조건"이라는 새로운 섹션이 있습니다.

정의 WITHOUT을 추가하기 만하면 -D됩니다.


디버그 및 릴리스를위한 두 개의 ROW가 있다고 언급 해 주셔서 감사합니다
— Yitzchak

누구든지 릴리스에서 이것을 테스트 했습니까?
— Glenn

빠른 사용자를위한 업데이트 된 답변입니다. 즉없이 -D.
— Mani

46

활성 컴파일 조건에 따른 isDebug 상수

#if코드베이스 전체에 조건을 적용 하지 않고 함수에 전달할 수있는 부울을 초래하는 또 다른 더 간단한 솔루션은 DEBUG프로젝트 빌드 대상 중 하나로 정의 Active Compilation Conditions하고 다음을 포함하는 것입니다 (전역 상수로 정의).

#if DEBUG
    let isDebug = true
#else
    let isDebug = false
#endif

컴파일러 최적화 설정에 따른 isDebug 상수

이 개념은 kennyTM의 답변을 기반으로합니다.

kennyTM와 비교할 때 주요 이점은 개인 또는 문서화되지 않은 방법에 의존하지 않는다는 것입니다.

에서 스위프트 4 :

let isDebug: Bool = {
    var isDebug = false
    // function with a side effect and Bool return value that we can pass into assert()
    func set(debug: Bool) -> Bool {
        isDebug = debug
        return isDebug
    }
    // assert:
    // "Condition is only evaluated in playgrounds and -Onone builds."
    // so isDebug is never changed to true in Release builds
    assert(set(debug: true))
    return isDebug
}()

전처리 매크로와 비교 하고 kennytm의 대답은 ,

  • ✓ -D DEBUG사용하기 위해 커스텀 플래그를 정의 할 필요가 없습니다.
  • ~ 실제로 Xcode 빌드 구성이 아닌 최적화 설정 측면에서 정의됩니다.
  • ✓ 문서화 됨 : 함수가 정상적인 API 릴리스 / 비추천 패턴을 따릅니다.

  • ✓ if / else를 사용해도 "실행되지 않습니다"경고가 발생 하지 않습니다 .


25

Moignans는 여기에서 대답 합니다. 도움이 될만한 정보의 또 다른 평화가 있습니다.

#if DEBUG
    let a = 2
#else
    let a = 3
#endif

아래와 같이 매크로를 부정 할 수 있습니다.

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif

23

Xcode 버전 9.4.1, Swift 4.1로 작성된 Swift 프로젝트에서

#if DEBUG
#endif

전 처리기 매크로에서 DEBUG = 1이 Xcode에 의해 이미 설정되어 있기 때문에 기본적으로 작동합니다.

따라서 #if DEBUG "out of box"를 사용할 수 있습니다.

그런데 조건 컴파일 블록을 일반적으로 사용하는 방법은 Apple의 책 Swift Programming Language 4.1 (컴파일러 제어 명령문 섹션)에 작성되어 있으며 컴파일 플래그를 작성하는 방법과 Swift의 C 매크로에 대응하는 내용은 다음과 같습니다. 다른 Apple의 책 Cocoa 및 Objective C에서 Swift 사용하기 (전 처리기 지시문 섹션에 있음)

앞으로 애플은 더 자세한 내용과 책에 대한 색인을 작성할 것입니다.


17

XCODE 9 이상

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif

3
와우 그것은 내가 본 것 중 가장 추악한 약어이다 : p
— rmp251

7

빌드 설정 DEBUG=1에서 설정 한 후이 GCC_PREPROCESSOR_DEFINITIONS호출을 수행하는 기능을 선호합니다.

func executeInProduction(_ block: () -> Void)
{
    #if !DEBUG
        block()
    #endif
}

그런 다음 디버그 빌드에서 생략하려는 블록을이 함수에 넣습니다.

executeInProduction {
    Fabric.with([Crashlytics.self]) // Compiler checks this line even in Debug
}

다음과 비교할 때 장점 :

#if !DEBUG
    Fabric.with([Crashlytics.self]) // This is not checked, may not compile in non-Debug builds
#endif

컴파일러가 내 코드의 구문을 확인하므로 구문이 정확하고 빌드되어 있는지 확인합니다.



2
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

출처


1
이것은 조건부 컴파일이 아닙니다. 유용하지만 그저 평범한 오래된 런타임 조건부입니다. OP는 메타 프로그래밍 목적으로 컴파일 타임을 요구합니다
— Shayne

3
@inlinable앞에 추가하면 funcSwift에서 가장 우아하고 관용적 인 방법입니다. 릴리스 빌드에서는 code()블록이 완전히 최적화되고 제거됩니다. Apple의 자체 NIO 프레임 워크에서도 비슷한 기능이 사용됩니다.
— mojuba

1

이것은 디버그에 대해서만 실행되는 assert에 의존하는 Jon Willis의 답변을 기반으로합니다 .

func Log(_ str: String) { 
    assert(DebugLog(str)) 
}
func DebugLog(_ str: String) -> Bool { 
    print(str) 
    return true
}

내 유스 케이스는 인쇄 진술을 기록하는 것입니다. 다음은 iPhone X의 릴리스 버전에 대한 벤치 마크입니다.

let iterations = 100_000_000
let time1 = CFAbsoluteTimeGetCurrent()
for i in 0 ..< iterations {
    Log ("⧉ unarchiveArray:\(fileName) memoryTime:\(memoryTime) count:\(array.count)")
}
var time2 = CFAbsoluteTimeGetCurrent()
print ("Log: \(time2-time1)" )

인쇄물:

Log: 0.0

Swift 4가 함수 호출을 완전히 제거하는 것처럼 보입니다.


디버그가 아닌 경우 함수가 비어 있기 때문에 호출을 완전히 제거하는 것처럼 제거합니까? 그거 정말 완벽 할것 같아.
— 요한
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.