Cara mendeteksi apakah aplikasi sedang dibangun untuk perangkat atau simulator di Swift

277

Di Objective-C kita bisa tahu apakah aplikasi sedang dibangun untuk perangkat atau simulator menggunakan makro:

#if TARGET_IPHONE_SIMULATOR
    // Simulator
#else
    // Device
#endif

Ini adalah kompilasi makro waktu dan tidak tersedia saat runtime.

Bagaimana saya bisa mencapai hal yang sama di Swift?

RaffAl
sumber
2
Itu bukan cara mendeteksi simulator atau perangkat nyata saat runtime di Objective-C. Itu adalah arahan kompiler yang menghasilkan kode berbeda tergantung pada build.
rmaddy
Terima kasih. Saya mengedit pertanyaan saya.
RaffAl
9
JAWABAN PILIHAN TERTINGGI BUKAN CARA TERBAIK UNTUK MEMECAHKAN MASALAH INI! jawaban mbelsky (saat ini sangat jauh ke bawah) adalah satu-satunya solusi yang datang tanpa jebakan. Bahkan Greg Parker dari Apple menyarankan untuk melakukannya seperti itu: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
BAHKAN DI CAPS, SANGAT SESUAI UNTUK MENYARANKAN BAHWA ADA YANG SALAH DENGAN PERIKSA RUNTIM. Saran oleh para insinyur Apple sering kali dianggap sebagai sampah yang tidak dipikirkan dengan baik, atau hanya berlaku dalam situasi tertentu, sehingga diri sendiri tidak berarti apa-apa.
Fattie
1
@Fattie: Akan menarik untuk mengetahui mengapa tidak ada jawaban yang diberikan memuaskan kebutuhan Anda, dan apa yang sebenarnya Anda harapkan dengan menawarkan hadiah.
Martin R

Jawaban:

364

Perbarui 30/01/19

Meskipun jawaban ini dapat berfungsi, solusi yang disarankan untuk pemeriksaan statis (seperti yang diklarifikasi oleh beberapa insinyur Apple) adalah dengan menentukan flag kompiler khusus yang menargetkan iOS Simulators. Untuk instruksi terperinci tentang bagaimana melakukannya, lihat jawaban @ mbelsky .

Jawaban asli

Jika Anda memerlukan pemeriksaan statis (mis. Bukan runtime jika / selain itu) Anda tidak dapat mendeteksi simulator secara langsung, tetapi Anda dapat mendeteksi iOS pada arsitektur desktop seperti berikut

#if (arch(i386) || arch(x86_64)) && os(iOS)
    ...
#endif

Setelah versi Swift 4.1

Penggunaan terbaru, sekarang langsung untuk semua dalam satu kondisi untuk semua jenis simulator hanya perlu menerapkan satu kondisi -

#if targetEnvironment(simulator)
  // your simulator code
#else
  // your real device code
#endif

Untuk klarifikasi lebih lanjut, Anda dapat memeriksa proposal Swift SE-0190


Untuk versi yang lebih lama -

Jelas, ini salah pada perangkat, tetapi mengembalikan true untuk iOS Simulator, seperti yang ditentukan dalam dokumentasi :

Konfigurasi pembangunan lengkungan (i386) mengembalikan true ketika kode dikompilasi untuk simulator iOS 32-bit.

Jika Anda mengembangkan untuk simulator selain iOS, Anda dapat dengan mudah memvariasikan osparameter: mis

Deteksi simulator watchOS

#if (arch(i386) || arch(x86_64)) && os(watchOS)
...
#endif

Deteksi simulator tvOS

#if (arch(i386) || arch(x86_64)) && os(tvOS)
...
#endif

Atau, bahkan, mendeteksi simulator apa pun

#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(watchOS) || os(tvOS))
...
#endif

Jika Anda tidak keberatan dengan pemeriksaan runtime, Anda dapat memeriksa TARGET_OS_SIMULATORvariabel (atau TARGET_IPHONE_SIMULATORdi iOS 8 dan di bawah), yang benar pada simulator.

Harap perhatikan bahwa ini berbeda dan sedikit lebih terbatas daripada menggunakan bendera preprosesor. Misalnya Anda tidak akan dapat menggunakannya di tempat di mana if/elsesintaksis tidak valid (misalnya di luar cakupan fungsi).

Katakan, misalnya, bahwa Anda ingin memiliki impor yang berbeda pada perangkat dan pada simulator. Ini tidak mungkin dengan pemeriksaan dinamis, sedangkan itu sepele dengan pemeriksaan statis.

#if (arch(i386) || arch(x86_64)) && os(iOS)
  import Foo
#else
  import Bar
#endif

Juga, karena flag diganti dengan a 0atau a 1oleh preprocessor cepat, jika Anda langsung menggunakannya dalam if/elseekspresi kompiler akan memunculkan peringatan tentang kode yang tidak terjangkau.

Untuk mengatasi peringatan ini, lihat salah satu jawaban lainnya.

Gabriele Petronella
sumber
1
Lebih banyak membaca di sini . Dan agar lebih membatasi, Anda bisa menggunakannya arch(i386) && os(iOS).
ahruss
1
Ini tidak berhasil untuk saya. Saya harus memeriksa i386 dan x86_64
akaru
3
JAWABAN INI BUKAN CARA TERBAIK UNTUK MEMECAHKAN MASALAH INI! jawaban mbelsky (saat ini sangat jauh ke bawah) adalah satu-satunya solusi yang datang tanpa jebakan. Bahkan Greg Parker dari Apple menyarankan untuk melakukannya seperti itu: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@russbishop ini terbukti menjadi saran yang bermanfaat bagi ratusan orang sejauh ini, mengimbangi hilangnya API. Alih-alih membajak jawaban dengan menandatangani komentar di atas, komunikasikan saja. Saya memperbarui jawaban untuk mengklarifikasi ini bukan lagi solusi terbaru dan saya telah memberikan tautan ke salah satu yang terlihat lebih benar.
Gabriele Petronella
9
Di Swift 4.1, Anda akan dapat mengatakan #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/proposals/… )
Hamish
172

LUAR BIASA UNTUK SWIFT 4.1. Gunakan #if targetEnvironment(simulator)sebagai gantinya. Sumber

Untuk mendeteksi simulator di Swift, Anda dapat menggunakan konfigurasi bangunan:

  • Tentukan konfigurasi ini -D IOS_SIMULATOR di Swift Compiler - Custom Flags> Other Swift Flags
  • Pilih SDK Simulator iOS mana pun di drop down iniDaftar drop-down

Sekarang Anda dapat menggunakan pernyataan ini untuk mendeteksi simulator:

#if IOS_SIMULATOR
    print("It's an iOS Simulator")
#else
    print("It's a device")
#endif

Anda juga dapat memperluas kelas UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator
mbelsky
sumber
8
Ini harus menjadi jawaban terbaik! Bahkan Greg Parker dari Apple menyarankan seperti itu: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
pembaruan penggunaan untuk swift 3: UIDevice.current.isSimulator
tylernol
1
Bolehkah saya bertanya mengapa jika saya menambahkan ini di bawah Rilis ini tidak berfungsi?
William Hu
3
Ini satu-satunya jawaban yang benar. Anda juga dapat mengatur ini dalam xcconfigfile dengan menggunakan OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDdan OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORmenimpa untuk Simulator.
russbishop
1
Pada Xcode 9.2, jawaban ini gagal untuk mengkompilasi beberapa waktu. Menghapus "-" sebelum "D" memecahkan masalah bagi saya.
Blake
160

Info Diperbarui per 20 Februari 2018

Sepertinya @russbishop memiliki jawaban otoritatif yang menjadikan jawaban ini "salah" - meskipun tampaknya bekerja lama sekali.

Mendeteksi jika aplikasi sedang dibangun untuk perangkat atau simulator di Swift

Jawaban Sebelumnya

Berdasarkan jawaban @ WZW dan komentar @ Pang, saya membuat sebuah utilitas utilitas sederhana. Solusi ini menghindari peringatan yang dihasilkan oleh jawaban @ WZW.

import Foundation

struct Platform {

    static var isSimulator: Bool {
        return TARGET_OS_SIMULATOR != 0
    }

}

Contoh penggunaan:

if Platform.isSimulator {
    print("Running on Simulator")
}
Daniel
sumber
10
Solusi yang jauh lebih baik daripada yang diterima. Memang jika suatu hari (walaupun sangat tidak mungkin) Apple memutuskan untuk menggunakan i386 atau x85_64 pada perangkat iOS, jawaban yang diterima tidak akan berfungsi ... atau bahkan jika komputer desktop mendapatkan proc baru!
Frizlab
2
Mengonfirmasi bahwa ini berfungsi dengan baik pada Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... hal yang sama, disederhanakan. Terima kasih +1
Dan Rosenstark
1
@aniel Ini berfungsi dengan baik dan sebenarnya lebih mudah daripada solusi saya. Namun perlu dicatat bahwa ini lebih terbatas daripada langkah preprosesor yang sebenarnya. Jika Anda memerlukan beberapa bagian kode untuk tidak dimasukkan dalam target (mis. Anda ingin memilih antara dua impor pada waktu kompilasi), Anda harus menggunakan pemeriksaan statis. Saya telah mengedit jawaban saya untuk menyoroti perbedaan ini.
Gabriele Petronella
JAWABAN INI BUKAN CARA TERBAIK UNTUK MEMECAHKAN MASALAH INI! jawaban mbelsky (saat ini sangat jauh ke bawah) adalah satu-satunya solusi yang datang tanpa jebakan. Bahkan Greg Parker dari Apple menyarankan untuk melakukannya seperti itu: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@Fattie TARGET_OS_SIMULATOR != 0adalah sudah dalam jawabannya . Ini solusi yang diberikan oleh Daniel. Tidak perlu menambahkannya lagi dalam variabel gratis, itu sudah ada di sana. Jika Anda berpikir memilikinya dalam suatu struct adalah buruk dan memilikinya dalam variabel bebas lebih baik maka posting komentar tentang ini atau buat jawaban Anda sendiri. Terima kasih.
Eric Aya
69

Dari Xcode 9.3

#if targetEnvironment(simulator)

Swift mendukung targetEnvironment kondisi platform baru dengan simulator argumen tunggal yang valid. Kompilasi bersyarat dari bentuk '#jika targetEnvironment (simulator)' sekarang dapat digunakan untuk mendeteksi kapan target build adalah simulator. Kompiler Swift akan berusaha mendeteksi, memperingatkan, dan menyarankan penggunaan targetEnvironment (simulator) ketika mengevaluasi kondisi platform yang tampaknya menguji lingkungan simulator secara tidak langsung, melalui kondisi platform os () dan arch () yang ada. (SE-0190)

iOS 9+:

extension UIDevice {
    static var isSimulator: Bool {
        return NSProcessInfo.processInfo().environment["SIMULATOR_DEVICE_NAME"] != nil
    }
}

Swift 3:

extension UIDevice {
    static var isSimulator: Bool {
        return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
    }
}

Sebelum iOS 9:

extension UIDevice {
    static var isSimulator: Bool {
        return UIDevice.currentDevice().model == "iPhone Simulator"
    }
}

Tujuan-C:

@interface UIDevice (Additions)
- (BOOL)isSimulator;
@end

@implementation UIDevice (Additions)

- (BOOL)isSimulator {
    if([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){9, 0, 0}]) {
        return [NSProcessInfo processInfo].environment[@"SIMULATOR_DEVICE_NAME"] != nil;
    } else {
        return [[self model] isEqualToString:@"iPhone Simulator"];
    }
}

@end
HotJard
sumber
2
Membandingkan string lebih rapuh dengan menggunakan konstanta yang didefinisikan.
Michael Peterson
@ P1X3L5 Anda benar! Tapi saya berasumsi bahwa metode ini disebut dalam mode debug - tidak bisa begitu solid, tetapi cepat untuk ditambahkan ke proyek
HotJard
1
@ Manant terima kasih atas tanggapannya. Saya telah memperbaiki kode
HotJard
@HotJard bagus, yang ini tidak menghasilkan will never be executedperingatan
Dannie P
59

Cepat 4

Anda sekarang dapat menggunakan targetEnvironment(simulator)sebagai argumen.

#if targetEnvironment(simulator)
    // Simulator
#else
    // Device
#endif

Diperbarui untuk Xcode 9.3

Matt Swift
sumber
8
Sekarang ini seharusnya jawaban yang diterima. Saya berharap ada cara di SO untuk mengusulkan jawaban yang disarankan baru berdasarkan pembaruan ke OS / bahasa pemrograman.
quemeful
4
itu adalah titik yang bagus @ quemeful - itu salah satu dari beberapa kegagalan dasar SO. Karena sistem komputasi berubah begitu cepat, hampir setiap jawaban pada SO menjadi salah seiring waktu .
Fattie
40

Izinkan saya mengklarifikasi beberapa hal di sini:

  1. TARGET_OS_SIMULATORtidak diatur dalam kode Swift dalam banyak kasus; Anda mungkin secara tidak sengaja mengimpornya karena header penghubung tetapi ini rapuh dan tidak didukung. Bahkan juga tidak mungkin dalam kerangka kerja. Inilah sebabnya mengapa beberapa orang bingung tentang apakah ini berfungsi di Swift.
  2. Saya sangat menyarankan agar tidak menggunakan arsitektur sebagai pengganti simulator.

Untuk melakukan pemeriksaan dinamis:

Memeriksa ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil baik-baik saja.

Anda juga bisa mendapatkan model dasar yang disimulasikan dengan memeriksa SIMULATOR_MODEL_IDENTIFIERyang akan mengembalikan string sepertiiPhone10,3 .

Untuk melakukan pemeriksaan statis:

Xcode 9.2 & sebelumnya: tentukan flag kompilasi Swift Anda sendiri (seperti yang ditunjukkan pada jawaban lain).

Xcode 9.3+ menggunakan kondisi targetEnvironment baru:

#if targetEnvironment(simulator)
    // for sim only
#else
    // for device
#endif
russbishop
sumber
1
Sepertinya Anda memiliki beberapa info orang dalam di sini. Sangat membantu! Catatan TARGET_OS_SIMULATOR berfungsi cukup lama di kode aplikasi dan kerangka kerja; dan itu juga berfungsi di Xcode 9.3 b3. Tapi, saya kira ini "kebetulan". Agak menyedihkan; karena ini sepertinya cara yang paling tidak berantakan. Sebagai penyedia kode kerangka kerja yang dapat dikompilasi dalam Xcode 9.3 atau yang lebih lama, sepertinya kita harus membungkus #jika targetEnvironment ... dalam makro #jika swift (> = 4.1) untuk menghindari kesalahan kompilator. Atau saya kira menggunakan .... lingkungan ["SIMULATOR_DEVICE_NAME"]! = Nihil. Pemeriksaan ini sepertinya lebih meretas, IMO.
Daniel
jika ada kesalahan "Kondisi platform tidak terduga (diharapkan 'os', 'arch', atau 'swift')" menggunakan targetEnvironment (simulator)
Zaporozhchenko Oleksandr
@Alexandr targetEnvironmentmendarat di Xcode 9.3. Anda memerlukan versi Xcode yang lebih baru.
russbishop
@russbishop kerja bagus membersihkan ini untuk era baru terbaru - terima kasih!
Fattie
Saya mengirim 250 hadiah, karena jawaban ini sepertinya menambah informasi paling baru dan terbaru - tepuk tangan
Fattie
15

Apa yang bekerja untuk saya sejak Swift 1.0 sedang memeriksa arsitektur selain lengan:

#if arch(i386) || arch(x86_64)

     //simulator
#else 
     //device

#endif
akaru
sumber
14

Runtime, tetapi lebih sederhana daripada sebagian besar solusi lain di sini:

if TARGET_OS_SIMULATOR != 0 {
    // target is current running in the simulator
}

Atau, Anda bisa memanggil fungsi pembantu Objective-C yang mengembalikan boolean yang menggunakan makro preprocessor (terutama jika Anda sudah melakukan mixing dalam proyek Anda).

Sunting: Bukan solusi terbaik, terutama pada Xcode 9.3. Lihat jawaban HotJard

shim
sumber
3
Saya melakukan ini tetapi mendapat peringatan di klausa lain karena "tidak akan pernah dieksekusi". Kami memiliki aturan peringatan nol, jadi :-(
EricS
itu akan menampilkan peringatan tetapi masuk akal, tergantung jika Anda memiliki simulator atau perangkat yang dipilih untuk membangun, peringatan tersebut akan menunjukkan pada bagian yang tidak akan dieksekusi, tapi ya mengganggu kebijakan peringatan nol
Fonix
1
Hanya melihat peringatan saat saya menggunakan == 0bukan != 0. Menggunakannya seperti yang tertulis di atas, bahkan dengan elseblok setelahnya, tidak menghasilkan peringatan apa pun di Swift 4 Xcode Versi 9.2 (9C40b)
shim
Saya juga mengujinya berjalan pada target simulator serta perangkat fisik. Tampaknya juga sama di Swift 3.2 (versi Xcode yang sama).
shim
Dalam Xcode 9.3 + Swift 4.1 saya baru saja memperhatikan bahwa ia memiliki peringatan bahkan dengan! = 0. Sheesh.
shim
10

Dalam sistem modern:

#if targetEnvironment(simulator)
    // sim
#else
    // device
#endif

Itu mudah.

Fattie
sumber
1
Tidak yakin mengapa yang pertama harus "lebih benar" daripada jawaban Daniel . - Perhatikan bahwa yang kedua adalah pemeriksaan waktu kompilasi. Selamat Tahun Baru!
Martin R
5

TARGET_IPHONE_SIMULATORsudah ditinggalkan di iOS 9. TARGET_OS_SIMULATORadalah penggantinya. JugaTARGET_OS_EMBEDDED tersedia.

Dari TargetConditionals.h :

#if defined(__GNUC__) && ( defined(__APPLE_CPP__) || defined(__APPLE_CC__) || defined(__MACOS_CLASSIC__) )
. . .
#define TARGET_OS_SIMULATOR         0
#define TARGET_OS_EMBEDDED          1 
#define TARGET_IPHONE_SIMULATOR     TARGET_OS_SIMULATOR /* deprecated */
#define TARGET_OS_NANO              TARGET_OS_WATCH /* deprecated */ 
Nuthatch
sumber
1
saya mencoba TARGET_OS_SIMULATOR tetapi tidak berhasil atau dikenali oleh Xcode sementara TARGET_IPHONE_SIMULATOR melakukannya. Saya sedang membangun untuk iOS 8.0 di atas.
CodeOverRide
Saya sedang melihat header iOS 9. Saya akan memperbarui jawaban saya.
Nuthatch
5

Saya harap ekstensi ini berguna.

extension UIDevice {
    static var isSimulator: Bool = {
        #if targetEnvironment(simulator)
        return true
        #else
        return false
        #endif
    }()
}

Pemakaian:

if UIDevice.isSimulator {
    print("running on simulator")
}
Lucas Chwe
sumber
@ ChetanKoli, saya akan membuat kode sangat jelas, daripada pendek, jadi mudah dimengerti bagi siapa pun. Tidak yakin bagaimana perasaan saya tentang hasil edit Anda.
Lucas Chwe
3

Dalam Xcode 7.2 (dan sebelumnya tetapi saya belum menguji seberapa awal), Anda dapat mengatur flag build khusus platform "-D TARGET_IPHONE_SIMULATOR" untuk "Any iOS Simulator".

Lihat di pengaturan pembangunan proyek di bawah "Kompresor Swift - Bendera Pelanggan" dan kemudian atur bendera di "Bendera Swift Lain". Anda dapat mengatur flag khusus platform dengan mengklik ikon 'plus' saat Anda mengarahkan kursor ke konfigurasi build.

Ada beberapa keuntungan melakukannya dengan cara ini: 1) Anda dapat menggunakan tes kondisional yang sama ("#jika TARGET_IPHONE_SIMULATOR") dalam kode Swift dan Objective-C Anda. 2) Anda bisa mengkompilasi variabel yang hanya berlaku untuk setiap build.

Tangkapan layar pengaturan pengaturan Xcode

xgerrit
sumber
1

Saya menggunakan kode di bawah ini di Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}
ak_ninan
sumber
1
Saya melakukan ini tetapi mendapat peringatan di klausa lain karena "tidak akan pernah dieksekusi". Kami memiliki aturan peringatan nol, jadi grrrr ....
EricS
Ini akan menampilkan peringatan setiap kali Anda mencoba untuk menjalankan dengan perangkat, jika Anda dipilih simulator untuk menjalankannya tidak akan menampilkan peringatan.
ak_ninan
1
sudah tidak digunakan lagi
rcmstark
1

Swift 4:

Saat ini, saya lebih suka menggunakan kelas ProcessInfo untuk mengetahui apakah perangkat tersebut merupakan simulator dan jenis perangkat apa yang digunakan:

if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
            print("yes is a simulator :\(simModelCode)")
}

Tetapi, seperti yang Anda ketahui, simModelCodebukan kode yang nyaman untuk memahami dengan segera simulator jenis apa yang diluncurkan, jadi, jika perlu, Anda dapat mencoba melihat jawaban SO lainnya untuk menentukan model iPhone / perangkat saat ini dan untuk memiliki lebih manusiawi string yang bisa dibaca.

Alessandro Ornano
sumber
1

Berikut adalah contoh Xcode 11 Swift yang didasarkan pada jawaban HotJard yang luar biasa di atas , ini juga menambahkan isDeviceBool dan menggunakan SIMULATOR_UDIDbukan nama. Tugas variabel dilakukan pada setiap baris sehingga Anda bisa lebih mudah memeriksanya di debugger jika Anda mau.

import Foundation

// Extensions to UIDevice based on ProcessInfo.processInfo.environment keys
// to determine if the app is running on an actual device or the Simulator.

@objc extension UIDevice {
    static var isSimulator: Bool {
        let environment = ProcessInfo.processInfo.environment
        let isSimulator = environment["SIMULATOR_UDID"] != nil
        return isSimulator
    }

    static var isDevice: Bool {
        let environment = ProcessInfo.processInfo.environment
        let isDevice = environment["SIMULATOR_UDID"] == nil
        return isDevice
    }
}

Ada juga entri kamus DTPlatformNameyang harus berisi simulator.

Alex Zavatone
sumber
0

Gunakan kode di bawah ini:

#if targetEnvironment(simulator)
   // Simulator
#else
   // Device
#endif

Bekerja untuk Swift 4danXcode 9.4.1

Haroldo Gondim
sumber
0

Xcode 11, Swift 5

    #if !targetEnvironment(macCatalyst)
    #if targetEnvironment(simulator)
        true
    #else
        false        
    #endif
    #endif
Pekerjaan yang belum dipetakan
sumber
0

Selain jawaban lainnya.

Di Objective-c, Pastikan Anda menyertakan TargetConditionals .

#include <TargetConditionals.h>

sebelum digunakan TARGET_OS_SIMULATOR.

M. Ali
sumber