Penggantian #ifdef dalam bahasa Swift

734

Dalam C / C ++ / Objective C Anda dapat mendefinisikan makro menggunakan preprosesor kompiler. Selain itu, Anda dapat menyertakan / mengecualikan beberapa bagian kode menggunakan preprosesor kompiler.

#ifdef DEBUG
    // Debug-only code
#endif

Apakah ada solusi serupa di Swift?

mxg
sumber
1
Sebagai ide, Anda bisa memasukkan ini ke header bridging obj-c Anda ..
Matej
42
Anda benar-benar harus memberikan jawaban karena Anda memiliki beberapa pilihan, dan pertanyaan ini membuat Anda mendapat banyak suara.
David H

Jawaban:

1069

Ya kamu bisa melakukannya.

Di Swift Anda masih dapat menggunakan macro preprocessor "# if / # else / # endif" (meskipun lebih terbatas), sesuai dengan dokumen Apple . Ini sebuah contoh:

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

Sekarang, Anda harus mengatur simbol "DEBUG" di tempat lain. Atur di bagian "Swift Compiler - Custom Flags", baris "Other Swift Flags". Anda menambahkan simbol DEBUG dengan -D DEBUGentri.

Seperti biasa, Anda dapat mengatur nilai yang berbeda saat di Debug atau saat di Rilis.

Saya mengujinya dalam kode nyata dan berfungsi; tampaknya tidak diakui di taman bermain.

Anda dapat membaca posting asli saya di sini .


CATATAN PENTING: -DDEBUG=1 tidak berfungsi. Hanya -D DEBUGbekerja. Tampaknya kompiler mengabaikan bendera dengan nilai tertentu.

Jean Le Moignan
sumber
41
Ini adalah jawaban yang benar, meskipun harus dicatat bahwa Anda hanya dapat memeriksa keberadaan bendera tetapi bukan nilai tertentu.
Charles Harley
19
Catatan tambahan : Di atas menambahkan -D DEBUGseperti yang disebutkan di atas, Anda juga perlu mendefinisikan DEBUG=1di Apple LLVM 6.0 - Preprocessing-> Preprocessor Macros.
Matthew Quiros
38
Saya tidak dapat mengaktifkan ini sampai saya mengubah format -DDEBUGdari jawaban ini: stackoverflow.com/a/24112024/747369 .
Kramer
11
@MattQuiros Tidak perlu untuk menambahkan DEBUG=1ke Preprocessor Macros, jika Anda tidak ingin menggunakannya dalam kode Objective-C.
derpoliuk
7
@Daniel Anda dapat menggunakan operator boolean standar (mis: `
#jika DEBUG`
353

Sebagaimana dinyatakan dalam Apple Documents

Kompiler Swift tidak termasuk preprosesor. Alih-alih, itu mengambil keuntungan dari atribut waktu kompilasi, membangun konfigurasi, dan fitur bahasa untuk mencapai fungsi yang sama. Karena alasan ini, arahan preprosesor tidak diimpor dalam Swift.

Saya telah berhasil mencapai apa yang saya inginkan dengan menggunakan Konfigurasi Build khusus:

  1. Buka proyek Anda / pilih target Anda / Pengaturan Bangun / cari Bendera Kustom
  2. Untuk target yang Anda pilih, atur flag kustom Anda menggunakan -D awalan (tanpa spasi putih), untuk Debug dan Release
  3. Lakukan langkah-langkah di atas untuk setiap target yang Anda miliki

Inilah cara Anda memeriksa target:

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

masukkan deskripsi gambar di sini

Diuji menggunakan Swift 2.2

Andrej
sumber
4
1.dengan ruang kerja putih juga, 2.should mengatur bendera hanya untuk Debug?
mulai
3
@c0ming tergantung pada kebutuhan Anda, tetapi jika Anda menginginkan sesuatu terjadi hanya dalam mode debug, dan tidak dalam rilis, Anda perlu menghapus -DDEBUG dari Release.
cdf1982
1
Setelah saya mengatur bendera kustom -DLOCAL, pada saya #if LOCAl #else #endif, itu jatuh ke #elsebagian. Saya menggandakan target asli AppTargetdan mengganti namanya menjadi AppTargetLocal& mengatur bendera kustom.
Perwyl Liu
3
@Andrej apakah Anda tahu cara membuat XCTest mengenali flag kustom juga? Saya menyadari itu jatuh ke dalam #if LOCAL , hasil yang dimaksudkan ketika saya menjalankan dengan simulator dan jatuh ke dalam #else selama pengujian. Saya ingin itu jatuh ke dalam #if LOCALjuga selama pengujian.
Perwyl Liu
3
Ini harus menjadi jawaban yang diterima. Jawaban yang diterima saat ini salah untuk Swift karena hanya berlaku untuk Objective-C.
miken.mkndev
171

Dalam banyak situasi, Anda tidak benar-benar membutuhkan kompilasi bersyarat ; Anda hanya perlu perilaku kondisional yang dapat Anda aktifkan dan matikan. Untuk itu, Anda dapat menggunakan variabel lingkungan. Ini memiliki keuntungan besar yang sebenarnya tidak perlu Anda kompilasi ulang.

Anda dapat mengatur variabel lingkungan, dan dengan mudah mengaktifkan atau menonaktifkannya, di editor skema:

masukkan deskripsi gambar di sini

Anda dapat mengambil variabel lingkungan dengan NSProcessInfo:

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

Inilah contoh kehidupan nyata. Aplikasi saya hanya berjalan di perangkat, karena menggunakan perpustakaan musik, yang tidak ada di Simulator. Lalu, bagaimana cara mengambil screenshot di Simulator untuk perangkat yang bukan milik saya? Tanpa cuplikan layar itu, saya tidak bisa mengirim ke AppStore.

Saya membutuhkan data palsu dan cara pemrosesan yang berbeda . Saya memiliki dua variabel lingkungan: satu yang, ketika dinyalakan, memberi tahu aplikasi untuk menghasilkan data palsu dari data nyata saat berjalan di perangkat saya; yang lain, ketika dihidupkan, menggunakan data palsu (bukan perpustakaan musik yang hilang) saat berjalan di Simulator. Mengaktifkan / menonaktifkan mode khusus tersebut mudah berkat kotak centang variabel lingkungan di editor Skema. Dan bonusnya adalah saya tidak dapat menggunakannya secara tidak sengaja di build App Store saya, karena pengarsipan tidak memiliki variabel lingkungan.

matt
sumber
Entah mengapa variabel lingkungan saya kembali ke nol pada peluncuran aplikasi kedua
Eugene
60
Hati-hati : Variabel Lingkungan ditetapkan untuk semua konfigurasi bangunan, mereka tidak dapat ditetapkan untuk masing-masing konfigurasi. Jadi ini bukan solusi yang layak jika Anda memerlukan perilaku untuk berubah tergantung pada apakah itu rilis atau versi debug.
Eric
5
@Eric Setuju, tetapi tidak disetel untuk semua tindakan skema. Jadi Anda bisa melakukan satu hal pada build-and-run dan hal yang berbeda pada arsip, yang seringkali merupakan perbedaan kehidupan nyata yang ingin Anda gambar. Atau Anda dapat memiliki beberapa skema, yang juga merupakan pola umum kehidupan nyata. Plus, seperti yang saya katakan dalam jawaban saya, mengganti variabel lingkungan hidup dan mati dalam skema mudah.
matt
10
Variabel lingkungan TIDAK bekerja dalam mode arsip. Mereka hanya diterapkan ketika aplikasi diluncurkan dari XCode. Jika Anda mencoba mengaksesnya di perangkat, aplikasi akan macet. Menemukan cara yang sulit.
iupchris10
2
@ iupchris10 "Pengarsipan tidak memiliki variabel lingkungan" adalah kata-kata terakhir dari jawaban saya, di atas. Seperti yang saya katakan dalam jawaban saya, itu bagus . Itu intinya .
matt
160

Perubahan besar ifdefpenggantian muncul dengan Xcode 8. yaitu penggunaan Kondisi Kompilasi Aktif .

Lihat Membangun dan Menautkan dalam Catatan rilis Xcode 8 .

Pengaturan build baru

Pengaturan baru: SWIFT_ACTIVE_COMPILATION_CONDITIONS

Active Compilation Conditionsis a new build setting for passing conditional compilation flags to the Swift compiler.

Sebelumnya, kami harus mendeklarasikan flag kompilasi bersyarat Anda di bawah OTHER_SWIFT_FLAGS, ingat untuk menambahkan "-D" ke pengaturan. Misalnya, mengkompilasi dengan nilai MYFLAG secara kondisional:

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

Nilai untuk ditambahkan ke pengaturan -DMYFLAG

Sekarang kita hanya perlu meneruskan nilai MYFLAG ke pengaturan baru. Saatnya untuk memindahkan semua nilai kompilasi bersyarat itu!

Silakan merujuk ke tautan di bawah ini untuk lebih banyak fitur Pengaturan Swift dalam Xcode 8: http://www.miqu.me/blog/2016/07/31/xcode-8-new-build-settings-and-analyzer-improvements/

Dshah
sumber
Apakah ada cara untuk menonaktifkan satu set Kondisi Kompilasi Aktif pada waktu build? Saya perlu menonaktifkan kondisi DEBUG ketika membangun konfigurasi debug untuk pengujian.
Jonny
1
@ Jonny Satu-satunya cara saya menemukan adalah membuat konfigurasi build ke-3 untuk proyek. Dari Project> Info tab> Konfigurasi, tekan '+', lalu duplikat Debug. Anda kemudian dapat menyesuaikan Kondisi Kompilasi Aktif untuk konfigurasi ini. Jangan lupa mengedit Target> Skema pengujian untuk menggunakan konfigurasi bangunan baru!
matthias
1
Ini seharusnya jawaban yang benar..itu satu-satunya hal yang bekerja untuk saya di xCode 9 menggunakan Swift 4.x!
shokaveli
1
BTW, Di Xcode 9.3 Swift 4.1 DEBUG sudah ada di Kondisi Kompilasi Aktif dan Anda tidak perlu menambahkan apa pun untuk memeriksa konfigurasi DEBUG. Hanya # jika DEBUG dan # endif.
Denis Kutlubaev
Saya pikir ini di luar topik, dan hal yang buruk untuk dilakukan. Anda tidak ingin menonaktifkan Kondisi Kompilasi Aktif. Anda memerlukan konfigurasi baru dan berbeda untuk pengujian - yang TIDAK akan memiliki tag "Debug". Pelajari tentang skema.
Motti Shneor
93

Pada Swift 4.1, jika yang Anda butuhkan hanyalah memeriksa apakah kode tersebut dibangun dengan konfigurasi debug atau rilis, Anda dapat menggunakan fungsi bawaan:

  • _isDebugAssertConfiguration()(true ketika optimisasi diatur ke -Onone)
  • _isReleaseAssertConfiguration()(true ketika optimisasi diatur ke -O) (tidak tersedia di Swift 3+)
  • _isFastAssertConfiguration()(true ketika optimisasi diatur ke -Ounchecked)

misalnya

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

Dibandingkan dengan macro preprocessor,

  • ✓ Anda tidak perlu menentukan -D DEBUGbendera khusus untuk menggunakannya
  • ~ Ini sebenarnya didefinisikan dalam hal pengaturan optimasi, bukan konfigurasi build Xcode
  • ✗ Tidak terdokumentasi, yang berarti fungsi dapat dihapus dalam pembaruan apa pun (tetapi harus aman dari AppStore karena pengoptimal akan mengubahnya menjadi konstanta)

  • ✗ Menggunakan di jika / lain akan selalu menghasilkan peringatan "Tidak akan pernah dieksekusi".

kennytm
sumber
1
Apakah fungsi bawaan ini dievaluasi pada waktu kompilasi atau runtime?
ma11hew28
@MattDiPasquale waktu Optimasi. if _isDebugAssertConfiguration()akan dievaluasi if falsedalam mode rilis dan if truemode debug.
kennytm
2
Saya tidak bisa menggunakan fungsi-fungsi ini untuk memilih beberapa variabel debug-only dalam rilis.
Franklin Yu
3
Apakah fungsi-fungsi ini didokumentasikan di suatu tempat?
Tom Harrington
7
Pada Swift 3.0 & XCode 8, fungsi-fungsi ini tidak valid.
CodeBender
87

Xcode 8 ke atas

Gunakan pengaturan Kondisi Kompilasi Aktif dalam pengaturan Build / kompiler Swift - Bendera kustom .

  • Ini adalah pengaturan build baru untuk meneruskan flag kompilasi bersyarat ke kompiler Swift.
  • Add bendera sederhana seperti ini: ALPHA, BETAdll

Kemudian periksa dengan kondisi kompilasi seperti ini:

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

Kiat: Anda juga dapat menggunakan #if !ALPHAdll.

Jakub Truhlář
sumber
77

Tidak ada preprosesor Swift. (Untuk satu hal, penggantian kode arbitrer merusak jenis dan keamanan memori.)

Swift memang menyertakan opsi konfigurasi build-time, jadi Anda dapat memasukkan kode untuk platform tertentu atau membangun gaya secara kondisional atau sebagai respons terhadap flag yang Anda tentukan dengan -Darg compiler. Namun, tidak seperti C, bagian kode Anda yang dikondisikan secara kondisional harus lengkap secara sintaksis. Ada bagian tentang ini di Menggunakan Swift With Cocoa dan Objective-C .

Sebagai contoh:

#if os(iOS)
    let color = UIColor.redColor()
#else
    let color = NSColor.redColor()
#endif
rickster
sumber
34
"Untuk satu hal, penggantian kode arbitrer merusak jenis dan keamanan memori." Bukankah pre-prosesor melakukan tugasnya sebelum kompiler melakukannya (dengan demikian namanya)? Jadi semua cek ini masih bisa terjadi.
Thilo
10
@ Thilo Saya pikir apa yang rusak adalah dukungan IDE
Aleksandr Dubinsky
1
Saya pikir apa yang didapat @rickster adalah bahwa makro C Preprocessor tidak memiliki pemahaman tentang jenis dan kehadiran mereka akan melanggar persyaratan jenis Swift. Alasan makro bekerja di C adalah karena C memungkinkan konversi tipe implisit, yang berarti Anda dapat menempatkan di INT_CONSTmana saja dan floatditerima. Swift tidak akan mengizinkan ini. Juga, jika Anda bisa melakukan var floatVal = INT_CONSTitu pasti akan rusak di suatu tempat nanti ketika kompiler mengharapkan Inttetapi Anda menggunakannya sebagai Float(tipe floatValakan disimpulkan sebagai Int). 10 gips kemudian dan itu hanya pembersih untuk menghapus makro ...
Ephemera
Saya mencoba menggunakan ini tetapi sepertinya tidak berhasil, masih mengkompilasi kode Mac pada iOS build. Apakah ada layar pengaturan lain di suatu tempat yang harus diubah?
Maury Markowitz
1
@ Thilo Anda benar - prosesor pra tidak merusak semua jenis atau keamanan memori.
tcurdt
50

Dua sen saya untuk Xcode 8:

a) Bendera khusus menggunakan -Dawalan berfungsi dengan baik, tetapi ...

b) Penggunaan yang lebih sederhana:

Di Xcode 8 ada bagian baru: "Kondisi Kompilasi Aktif", sudah dengan dua baris, untuk debug dan rilis.

Cukup tambahkan definisi Anda TANPA -D.

ingconti
sumber
Terima kasih telah menyebutkan bahwa ada DUA BARIS UNTUK DEBUG DAN RELEASE
Yitzchak
Adakah yang menguji ini dalam rilis?
Glenn
Ini adalah jawaban yang diperbarui untuk pengguna cepat. yaitu tanpa -D.
Mani
46

isDebug Constant Berdasarkan Kondisi Kompilasi Aktif

Solusi lain, yang mungkin lebih sederhana, yang masih menghasilkan boolean yang dapat Anda gunakan menjadi fungsi tanpa menyertakan #ifpersyaratan di seluruh basis kode Anda adalah mendefinisikan DEBUGsebagai salah satu target membangun proyek Anda Active Compilation Conditionsdan memasukkan yang berikut (saya mendefinisikannya sebagai konstanta global):

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

isDebug Constant Berdasarkan pada Pengaturan Optimalisasi Kompiler

Konsep ini dibangun berdasarkan jawaban kennytm

Keuntungan utama ketika membandingkan dengan kennytm, adalah bahwa ini tidak bergantung pada metode pribadi atau tidak berdokumen.

Dalam 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
}()

Dibandingkan dengan macro preprocessor dan jawaban kennytm ,

  • ✓ Anda tidak perlu menentukan -D DEBUGbendera khusus untuk menggunakannya
  • ~ Ini sebenarnya didefinisikan dalam hal pengaturan optimasi, bukan konfigurasi build Xcode
  • Didokumentasikan , yang artinya fungsi akan mengikuti pola pelepasan / penghentian API yang normal.

  • ✓ Menggunakan in jika / tidak akan menghasilkan peringatan "Tidak akan pernah dieksekusi".

Jon Willis
sumber
25

Orang Moigna menjawab di sini berfungsi dengan baik. Ini adalah satu lagi kedamaian info jika itu membantu,

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

Anda dapat meniadakan makro seperti di bawah ini,

#if !RELEASE
    let a = 2
#else
    let a = 3
#endif
Sazzad Hissain Khan
sumber
23

Dalam proyek Swift yang dibuat dengan Xcode Versi 9.4.1, Swift 4.1

#if DEBUG
#endif

bekerja secara default karena dalam Preprocessor Macros DEBUG = 1 telah ditetapkan oleh Xcode.

Jadi, Anda dapat menggunakan #jika DEBUG "di luar kotak".

Ngomong-ngomong, bagaimana cara menggunakan blok kompilasi kondisi secara umum ditulis dalam buku Apple The Swift Programming Language 4.1 (bagian Pernyataan Kontrol Kompiler) dan bagaimana menulis flag kompilasi dan apa yang merupakan rekan dari makro C di Swift ditulis dalam buku Apple lainnya Menggunakan Swift dengan Kakao dan Sasaran C (di bagian Petunjuk Pengolah)

Harapan di masa depan Apple akan menulis konten yang lebih rinci dan indeks untuk buku-buku mereka.

Vadim Motorine
sumber
17

XCODE 9 DAN DI ATAS

#if DEVELOP
    //
#elseif PRODCTN
    //
#else
    //
#endif
midhun p
sumber
3
wow itu adalah singkatan paling jelek yang pernah saya lihat: p
rmp251
7

Setelah mengatur DEBUG=1di GCC_PREPROCESSOR_DEFINITIONSPengaturan Bangun saya, saya lebih suka menggunakan fungsi untuk melakukan panggilan ini:

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

Dan kemudian cukup lampirkan dalam fungsi ini setiap blok yang ingin saya hilangkan di Debug builds:

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

Keuntungannya jika dibandingkan dengan:

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

Apakah kompiler memeriksa sintaks kode saya, jadi saya yakin sintaksnya benar dan dibangun.

Rivera
sumber
3

! [Di Xcode 8 & di atas pergi ke pengaturan build -> cari flag kustom] 1

Dalam kode

 #if Live
    print("Live")
    #else
    print("debug")
    #endif
sachin_kvk
sumber
Anda telah menemukannya di sini! Swift #jika melihat bendera kustom BUKAN makro preprosesor. Harap perbarui jawaban Anda dengan konten dari tautan, sering kali tautan akan putus setelah beberapa saat
Dale
3
func inDebugBuilds(_ code: () -> Void) {
    assert({ code(); return true }())
}

Sumber

Adam Smaka
sumber
1
Ini bukan kompilasi bersyarat. Sementara berguna, itu hanya kondisi runtime lama biasa. OP meminta setelah kompiletime untuk tujuan metaprogramming
Shayne
3
Cukup tambahkan @inlinable di depan funcdan ini akan menjadi cara yang paling elegan dan idiomatis untuk Swift. Dalam rilis build, code()blok Anda akan dioptimalkan dan dihilangkan sama sekali. Fungsi serupa digunakan dalam kerangka NIO Apple sendiri.
mojuba
1

Ini didasarkan pada jawaban Jon Willis yang bergantung pada tegas, yang hanya dieksekusi dalam kompilasi Debug:

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

Kasus penggunaan saya adalah untuk mencatat pernyataan cetak. Berikut ini adalah patokan untuk versi Rilis di 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)" )

cetakan:

Log: 0.0

Sepertinya Swift 4 sepenuhnya menghilangkan pemanggilan fungsi.

Warren Stringer
sumber
Menghilangkan, seperti dalam menghapus panggilan secara keseluruhan ketika tidak di debug - karena fungsinya kosong? Itu akan sempurna.
Johan