Swift言語での#ifdefの置き換え


735

C / C ++ / Objective Cでは、コンパイラプリプロセッサを使用してマクロを定義できます。さらに、コンパイラプリプロセッサを使用して、コードの一部を含めたり除外したりできます。

#ifdef DEBUG
    // Debug-only code
#endif

Swiftにも同様のソリューションはありますか?


1
アイデアとして、あなたはあなたの中にOBJ-Cのブリッジのヘッダー。これを入れることができます
マテイ

42
いくつかの選択肢があるので、あなたは本当に答えを与えるべきです、そしてこの質問はあなたに多くの賛成票を得ました。
David H

回答:


1069

はい、できます。

Swiftでは、Appleのドキュメントに従って、「#if /#else /#endif」プリプロセッサマクロを使用できます(ただし、より制約されます)。次に例を示します。

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

ただし、「デバッグ」シンボルを他の場所に設定する必要があります。「Swift Compiler-Custom Flags」セクションの「Other Swift Flags」行で設定します。-D DEBUGエントリと共にDEBUGシンボルを追加します。

通常どおり、デバッグ時またはリリース時に別の値を設定できます。

私はそれを実際のコードでテストしましたが、うまくいきました。遊び場では認識されていないようです。

私の元の投稿はここで読むことができます


重要な注意: -DDEBUG=1機能しません。のみ-D DEBUG機能します。コンパイラは特定の値のフラグを無視しているようです。


41
これは正解ですが、特定の値ではなくフラグの存在のみを確認できることに注意してください。
Charles Harley

19
追加の注記-D DEBUG上記の追加に加えてDEBUG=1Apple LLVM 6.0 - Preprocessing-> でも定義する必要がありますPreprocessor Macros
Matthew Quiros、2015年

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.with white space work ,, 2.should set the flag only for Debug?
c0ming 2016

3
@ c0mingこれはニーズによって異なりますが、リリースモードではなくデバッグモードでのみ何かを実行したい場合は、リリースモードから-DDEBUGを削除する必要があります。
cdf1982

1
私はカスタムフラグを設定した後-DLOCAL、私には#if LOCAl #else #endif、それがに該当する#elseセクション。元のターゲットを複製してAppTarget名前を変更しAppTargetLocal、カスタムフラグを設定しました。
Perwyl Liu 2016

3
@Andrej XCTestにカスタムフラグも認識させる方法を知っていますか?私はそれがに当てはまることに気づき#if LOCAL ました。シミュレーターで実行したときに意図した結果であり、#else テスト中に当てはまります。#if LOCALテスト中にも落ち込んで欲しいです。
Perwyl Liu

3
これは受け入れられる答えになるはずです。現在受け入れられている回答は、Objective-Cにのみ適用されるため、Swiftでは正しくありません。
miken.mkndev 2016

171

多くの場合、条件付きコンパイルは実際には必要ありません。オンとオフを切り替えることができる条件付きの動作が必要なだけです。そのために、環境変数を使用できます。これには、実際に再コンパイルする必要がないという大きな利点があります。

スキームエディターで環境変数を設定し、簡単にオンまたはオフに切り替えることができます。

ここに画像の説明を入力してください

NSProcessInfoを使用して環境変数を取得できます。

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

これが実際の例です。私のアプリはデバイス上でのみ実行されます。これは、シミュレータには存在しない音楽ライブラリを使用しているためです。では、私が所有していないデバイスのシミュレータでスクリーンショットを撮るにはどうすればよいですか?これらのスクリーンショットがないと、AppStoreに送信できません。

偽のデータそれを処理する別の方法が必要です。私は2つの環境変数を使用しています。1つは、スイッチをオンにしたときに、デバイスで実行中に実際のデータから偽のデータを生成するようにアプリに指示します。もう1つは、スイッチをオンにすると、シミュレーターでの実行中に(欠落している音楽ライブラリではなく)偽のデータを使用します。Schemeエディターの環境変数のチェックボックスにより、これらの特別なモードのオン/オフを簡単に切り替えることができます。さらに、アーカイブには環境変数がないため、App Storeビルドで誤って使用することはできません。


何らかの理由で、2回目のアプリの起動時に環境変数がnilとして返されました
Eugene

60
気をつけろ:環境変数は、すべてのビルド構成用に設定されている、彼らは個々に設定することはできません。したがって、リリースかデバッグビルドかによって動作を変更する必要がある場合、これは実行可能なソリューションではありません
Eric

5
@Ericは同意しましたが、すべてのスキームアクションに設定されているわけではありません。したがって、ビルドと実行で1つのことを行い、アーカイブで別のことを行うことができます。これは、多くの場合、実際に区別したいものです。または、複数のスキームを使用することもできますが、これも実際の一般的なパターンです。さらに、私の回答で述べたように、スキームで環境変数のオンとオフを切り替えるのは簡単です。
マット

10
環境変数はアーカイブモードでは機能しません。XCodeからアプリを起動した場合にのみ適用されます。デバイスでこれらにアクセスしようとすると、アプリがクラッシュします。難しい方法を見つけました。
iupchris10 2015

2
@ iupchris10「アーカイブには環境変数がありません」は、上記の私の回答の最後の言葉です。私の回答で言うように、それは良いことです。それがポイントです。
マット2015

160

ifdefXcode 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ビルド設定機能の詳細については、以下のリンクを参照してください:http : //www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/


ビルド時に設定済みのアクティブコンパイル条件を無効にする方法はありますか?テスト用のデバッグ構成を構築するときに、デバッグ状態を無効にする必要があります。
ジョニー2017

1
@ジョニー私が見つけた唯一の方法は、プロジェクトの3番目のビルド構成を作成することです。[プロジェクト]> [情報]タブ> [構成]で[+]をクリックし、デバッグを複製します。その後、この構成のアクティブなコンパイル条件をカスタマイズできます。新しいビルド構成を使用するために、ターゲット>テストスキームを編集することを忘れないでください!
matthias

1
これは正しい答えです。Swift4.xを使用してxCode 9で動作したのはこれだけです。
shokaveli

1
ちなみに、Xcode 9.3では、Swift 4.1のDEBUGはすでにアクティブコンパイル条件にあり、デバッグ構成を確認するために何も追加する必要はありません。#if DEBUGと#endifだけです。
Denis Kutlubaev

これは主題外であり、悪いことだと思います。アクティブなコンパイル条件を無効にしたくない場合。テストには新しい別の設定が必要です-「デバッグ」タグはありません。スキームについて学ぶ。
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ビルド構成ではなく、最適化設定の観点から定義されています
  • ✗ドキュメント化されていません。つまり、更新時に関数を削除できます(ただし、オプティマイザがこれらを定数に変換するため、AppStoreセーフである必要があります)。

  • if if / elseで使用すると、常に「実行されない」という警告が生成されます。


1
これらの組み込み関数はコンパイル時または実行時に評価されますか?
ma11hew28

@MattDiPasquale最適化時間。リリースモードif _isDebugAssertConfiguration()で評価されif falseif trueデバッグモードです。
kennytm 2016年

2
ただし、これらの関数を使用して、リリース時にデバッグ専用変数をオプトアウトすることはできません。
フランクリンYu

3
これらの関数はどこかに文書化されていますか?
トムハリントン、

7
Swift 3.0およびXCode 8以降、これらの関数は無効です。
CodeBender 2016年

87

Xcode 8以降

ビルド設定/ Swiftコンパイラのカスタムフラグのアクティブコンパイル条件設定を使用します

  • これは、条件付きコンパイルフラグをSwiftコンパイラに渡すための新しいビルド設定です。
  • 次のような単純な追加フラグ:ALPHABETAなど

次に、次のようなコンパイル条件で確認します。

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

ヒント:#if !ALPHAなども使用できます。


77

Swiftプリプロセッサはありません。(1つには、任意のコード置換により、タイプおよびメモリの安全性が損なわれます。)

ただし、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ならどこにでも置くことができます。Swiftはこれを許可しません。また、var floatVal = INT_CONSTやむを得ず実行できる場合は、コンパイラーがを期待しているときに後でどこかでブレークダウンしIntますが、それをとして使用しますFloat(タイプはfloatValとして推論されますInt)。10キャスト後、マクロを削除するためのよりクリーンなもの
Ephemera

これを使用しようとしていますが、動作しないようです。iOSビルドでMacコードをコンパイルしています。どこかで調整する必要がある別のセットアップ画面はありますか?
Maury Markowitz、2015

1
@Thilo正解です-プリプロセッサはタイプやメモリの安全性を損なうことはありません
tcurdt

50

Xcode 8の私の2セント:

a)-Dプレフィックスを使用したカスタムフラグは正常に機能しますが...

b)より簡単な使用:

Xcode 8には、デバッグとリリース用の新しいセクション「アクティブコンパイル条件」が2行あります。

定義なしで定義を追加するだけ-Dです。


デバッグとリリースのための2つの行があると言及していただきありがとうございます
-Yitzchak

誰かがリリースでこれをテストしましたか?
Glenn

これは、迅速なユーザーのための更新された答えです。すなわちなし-D
Mani

46

アクティブなコンパイル条件に基づくisDebug定数

#ifコードベース全体に条件文を付けずに関数に渡すことができるブール値をもたらす、もう1つの、おそらくより単純なソリューションDEBUGは、プロジェクトビルドターゲットの1つとして定義し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の答えはうまくいきます。役立つ場合に備えて、もう1つの情報の平安があります。

#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プロジェクトでは、Swift 4.1

#if DEBUG
#endif

プリプロセッサマクロではDEBUG = 1がXcodeによってすでに設定されているため、デフォルトで機能します。

そのため、#if DEBUGを「そのまま」使用できます。

ちなみに、一般的な条件コンパイルブロックの使用方法は、Appleの書籍「The Swift Programming Language 4.1」(セクション「コンパイラ制御ステートメント」)に記述されており、コンパイルフラグの記述方法と、SwiftのCマクロに対応するものは、別のAppleの本 『ココアとObjective CでのSwiftの使用』(セクションプリプロセッサディレクティブ内)

今後Appleが彼らの本のより詳細な内容と索引を書いてくれることを願っています。



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

コンパイラがコードの構文をチェックするので、その構文が正しくビルドされていると確信しています。



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

ソース


1
これは条件付きコンパイルではありません。便利ですが、それは単なる古いランタイム条件です。OPはメタプログラミングの目的でコンパイル後に質問しています
Shayne

3
@inlinable前に追加するだけでfunc、これがSwiftの最もエレガントで慣用的な方法になります。リリースビルドでは、code()ブロックが最適化され、完全に削除されます。同様の関数がApple独自のNIOフレームワークで使用されています。
mojuba

1

これは、デバッグコンパイルでのみ実行されるアサートに依存するジョンウィリスの回答に基づいています。

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.