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 Conditionsis 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.