program story

Swift 언어의 #ifdef 대체

inputbox 2020. 9. 30. 10:40
반응형

Swift 언어의 #ifdef 대체


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

#ifdef DEBUG
    // Debug-only code
#endif

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


예, 할 수 있습니다.

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작동합니다. 컴파일러가 특정 값을 가진 플래그를 무시하는 것 같습니다.


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를 사용하여 테스트


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

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

여기에 이미지 설명 입력

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

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

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

가짜 데이터다른 처리 방법이 필요 합니다 . 두 가지 환경 변수가 있습니다. 하나는 스위치를 켰을 때 내 기기에서 실행되는 동안 실제 데이터에서 가짜 데이터를 생성하도록 앱에 지시합니다. 다른 하나는 전원을 켰을 때 시뮬레이터에서 실행되는 동안 가짜 데이터 (누락 된 음악 라이브러리가 아님)를 사용합니다. Scheme 편집기의 환경 변수 확인란 덕분에 각 특수 모드를 켜고 끄는 것이 쉽습니다. 그리고 보너스는 보관에 환경 변수가 없기 때문에 실수로 App Store 빌드에서 사용할 수 없다는 것입니다.


ifdefXcode 8 에서 교체 의 주요 변경 사항이 발생했습니다. 즉, Active Compilation Conditions 사용 .

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 기능은 아래 링크를 참조하십시오. http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/


Swift 4.1부터 필요한 것은 코드가 디버그 또는 릴리스 구성으로 빌드되었는지 확인하는 것뿐이라면 내장 함수를 사용할 수 있습니다.

  • _isDebugAssertConfiguration() (true when optimization is set to -Onone)
  • _isReleaseAssertConfiguration() (true when optimization is set to -O) (not available on Swift 3+)
  • _isFastAssertConfiguration() (true when optimization is set to -Ounchecked)

e.g.

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

Compared with preprocessor macros,

  • ✓ You don't need to define a custom -D DEBUG flag to use it
  • ~ It is actually defined in terms of optimization settings, not Xcode build configuration
  • ✗ Undocumented, which means the function can be removed in any update (but it should be AppStore-safe since the optimizer will turn these into constants)

  • ✗ Using in if/else will always generate a "Will never be executed" warning.


Xcode 8 and above

Use Active Compilation Conditions setting in Build settings / Swift compiler - Custom flags.

  • This is the new build setting for passing conditional compilation flags to the Swift compiler.
  • Simple add flags like this: ALPHA, BETA etc.

Then check it with compilation conditions like this:

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

Tip: You can also use #if !ALPHA etc.


There is no Swift preprocessor. (For one thing, arbitrary code substitution breaks type- and memory-safety.)

Swift does include build-time configuration options, though, so you can conditionally include code for certain platforms or build styles or in response to flags you define with -D compiler args. Unlike with C, though, a conditionally compiled section of your code must be syntactically complete. There's a section about this in Using Swift With Cocoa and Objective-C.

For example:

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

My two cents for Xcode 8:

a) A custom flag using the -D prefix works fine, but...

b) Simpler use:

In Xcode 8 there is a new section: "Active Compilation Conditions", already with two rows, for debug and release.

Simply add your define WITHOUT -D.


isDebug Constant Based on Active Compilation Conditions

Another, perhaps simpler, solution that still results in a boolean that you can pass into functions without peppering #if conditionals throughout your codebase is to define DEBUG as one of your project build target's Active Compilation Conditions and include the following (I define it as a global constant):

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

isDebug Constant Based on Compiler Optimization Settings

This concept builds on kennytm's answer

The main advantage when comparing against kennytm's, is that this does not rely on private or undocumented methods.

In Swift 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
}()

Compared with preprocessor macros and kennytm's answer,

  • ✓ You don't need to define a custom -D DEBUG flag to use it
  • ~ It is actually defined in terms of optimization settings, not Xcode build configuration
  • Documented, which means the function will follow normal API release/deprecation patterns.

  • ✓ Using in if/else will not generate a "Will never be executed" warning.


In Swift projects created with Xcode Version 9.4.1, Swift 4.1

#if DEBUG
#endif

works by default because in the Preprocessor Macros DEBUG=1 has already been set by Xcode.

So you can use #if DEBUG "out of box".

By the way, how to use the condition compilation blocks in general is written in Apple's book The Swift Programming Language 4.1 (the section Compiler Control Statements) and how to write the compile flags and what is counterpart of the C macros in Swift is written in another Apple's book Using Swift with Cocoa and Objective C (in the section Preprocessor Directives)

Hope in future Apple will write the more detailed contents and the indexes for their books.


XCODE 9 AND ABOVE

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

After setting DEBUG=1 in your GCC_PREPROCESSOR_DEFINITIONS Build Settings I prefer using a function to make this calls:

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

And then just enclose in this function any block that I want omitted in Debug builds:

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

The advantage when compared to:

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

Is that the compiler checks the syntax of my code, so I am sure that its syntax is correct and builds.


Moignans 대답은 여기에서 잘 작동합니다. 도움이 될 경우를 대비하여 또 다른 정보를 제공합니다.

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

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

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

! [Xcode 8 이상에서는 빌드 설정으로 이동-> 사용자 지정 플래그 검색] 1

코드에서

 #if Live
    print("Live")
    #else
    print("debug")
    #endif

이것은 디버그 컴파일에서만 실행되는 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가 함수 호출을 완전히 제거하는 것처럼 보입니다.


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

출처

참고 URL : https://stackoverflow.com/questions/24003291/ifdef-replacement-in-the-swift-language

반응형