Come rilevare se l'app è in fase di creazione per dispositivo o simulatore in Swift


277

In Objective-C possiamo sapere se un'app viene costruita per dispositivo o simulatore usando le macro:

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

Si tratta di macro di tempo di compilazione e non disponibili in fase di esecuzione.

Come posso ottenere lo stesso in Swift?


2
Questo non è come rilevare il simulatore o un dispositivo reale in fase di esecuzione in Objective-C. Quelle sono direttive del compilatore che producono codice diverso a seconda della build.
rmaddy,

Grazie. Ho modificato la mia domanda.
RaffAl,

9
LE RISPOSTE VOTATE PIÙ ALTE NON SONO IL MODO MIGLIORE PER RISOLVERE QUESTO PROBLEMA! La risposta di mbelsky (attualmente molto in basso) è l'unica soluzione che arriva senza insidie. Anche Greg Parker di Apple ha suggerito di farlo in questo modo: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt

1
ANCHE IN MAIUSCOLO, È NESSUNO SUGGERIRE CHE SIA QUALCOSA DI SBAGLIATO CON UN CONTROLLO RUNTIME. I suggerimenti degli ingegneri Apple sono spesso spazzatura mal pensata o si applicano solo in determinate situazioni, quindi se stessi significa meno di niente.
Fattie, il

1
@Fattie: Sarebbe interessante sapere perché nessuna delle risposte fornite soddisfa i tuoi bisogni e cosa speri esattamente offrendo la generosità.
Martin R,

Risposte:


364

Aggiornamento 30/01/19

Sebbene questa risposta possa funzionare, la soluzione consigliata per un controllo statico (come chiarito da diversi ingegneri Apple) è quella di definire un flag di compilatore personalizzato destinato ai simulatori iOS. Per istruzioni dettagliate su come procedere, vedere la risposta di @ mbelsky .

Risposta originale

Se hai bisogno di un controllo statico (ad es. Non un runtime if / else) non puoi rilevare direttamente il simulatore, ma puoi rilevare iOS su un'architettura desktop come segue

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

Dopo la versione Swift 4.1

Ultimo utilizzo, ora direttamente per tutti in una condizione per tutti i tipi di simulatori è necessario applicare una sola condizione:

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

Per ulteriori chiarimenti, è possibile controllare la proposta Swift SE-0190


Per la versione precedente -

Chiaramente, questo è falso su un dispositivo, ma restituisce true per il simulatore iOS, come specificato nella documentazione :

La configurazione di build arch (i386) restituisce true quando il codice viene compilato per il simulatore iOS a 32 bit.

Se stai sviluppando un simulatore diverso da iOS, puoi semplicemente variare il osparametro: ad es

Rileva il simulatore watchOS

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

Rileva il simulatore tvOS

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

O, persino, rilevare qualsiasi simulatore

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

Se invece stai bene con un controllo di runtime, puoi ispezionare la TARGET_OS_SIMULATORvariabile (o TARGET_IPHONE_SIMULATORin iOS 8 e precedenti), che è davvero su un simulatore.

Si noti che questo è diverso e leggermente più limitato rispetto all'utilizzo di un flag di preprocessore. Ad esempio, non sarai in grado di usarlo nel posto in cui if/elsesintatticamente non è valido (ad esempio al di fuori degli ambiti delle funzioni).

Supponiamo, ad esempio, che desideri avere importazioni diverse sul dispositivo e sul simulatore. Questo è impossibile con un controllo dinamico, mentre è banale con un controllo statico.

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

Inoltre, poiché il flag viene sostituito con 0ao 1dal rapido preprocessore, se lo si utilizza direttamente in if/elseun'espressione, il compilatore genererà un avviso sul codice non raggiungibile.

Per aggirare questo avviso, vedere una delle altre risposte.


1
Altre letture qui . E per essere ancora più restrittivi, potresti usare arch(i386) && os(iOS).
ahruss,

1
Questo non ha funzionato per me. Ho dovuto controllare sia i386 che x86_64
akaru il

3
QUESTA RISPOSTA NON È IL MODO MIGLIORE PER RISOLVERE QUESTO PROBLEMA! La risposta di mbelsky (attualmente molto in basso) è l'unica soluzione che arriva senza insidie. Anche Greg Parker di Apple ha suggerito di farlo in questo modo: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt

2
@russbishop questo si è rivelato un utile consiglio a centinaia di persone finora, compensando un'API mancante. Invece di dirottare la risposta firmando un commento in alto, basta comunicare. Ho aggiornato la risposta per chiarire che questa non è più una soluzione aggiornata e ho fornito un collegamento a quello che sembra più corretto.
Gabriele Petronella,

9
In Swift 4.1, sarai in grado di dire #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/proposals/… )
Hamish

172

Non aggiornato per Swift 4.1. Usa #if targetEnvironment(simulator)invece. fonte

Per rilevare il simulatore in Swift è possibile utilizzare la configurazione build:

  • Definire questa configurazione -D IOS_SIMULATOR nel compilatore Swift - Bandiere personalizzate> Altre bandiere Swift
  • Seleziona Qualsiasi SDK per iOS Simulator in questo menu a discesaMenu `A tendina

Ora puoi usare questa istruzione per rilevare il simulatore:

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

Inoltre puoi estendere la classe UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator

8
Questa dovrebbe essere la risposta migliore! Anche Greg Parker di Apple ha suggerito in questo modo: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt

1
aggiornamento sull'utilizzo di swift 3: UIDevice.current.isSimulator
tylernol

1
Posso chiederti perché se lo aggiungo in Release non funziona?
William Hu

3
Questa è l'unica risposta corretta. Puoi anche configurarlo in xcconfigfile usando OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDe OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORsovrascrivendo il Simulatore.
vescovo russo,

1
Su Xcode 9.2, questa risposta non è riuscita a compilare alcune volte. Rimozione del "-" prima della "D" risolto il problema per me.
Blake,

160

Informazioni aggiornate al 20 febbraio 2018

Sembra che @russbishop abbia una risposta autorevole che rende questa risposta "errata", anche se sembra funzionare a lungo.

Rileva se l'app è in fase di creazione per dispositivo o simulatore in Swift

Risposta precedente

Sulla base della risposta di @ WZW e dei commenti di @ Pang, ho creato una semplice struttura di utilità. Questa soluzione evita gli avvertimenti prodotti dalla risposta di @ WZW.

import Foundation

struct Platform {

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

}

Esempio di utilizzo:

if Platform.isSimulator {
    print("Running on Simulator")
}

10
Soluzione molto migliore di quella accettata. Infatti se un giorno (anche se è molto improbabile) Apple decidesse di utilizzare i386 o x85_64 su dispositivi iOS, la risposta accettata non funzionerà ... o anche se i computer desktop ottengono un nuovo proc!
Frizlab,

2
Confermato che funziona perfettamente su Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... stessa cosa, semplificato. +1 grazie
Dan Rosenstark

1
@daniel Funziona bene ed è in realtà più semplice della mia soluzione. Tuttavia, vale la pena notare che è più limitato di un passaggio del preprocessore effettivo. Se è necessario che una parte del codice non sia inclusa nella destinazione (ad esempio, si desidera scegliere tra due importazioni in fase di compilazione), è necessario utilizzare un controllo statico. Ho modificato la mia risposta per evidenziare questa differenza.
Gabriele Petronella,

QUESTA RISPOSTA NON È IL MODO MIGLIORE PER RISOLVERE QUESTO PROBLEMA! La risposta di mbelsky (attualmente molto in basso) è l'unica soluzione che arriva senza insidie. Anche Greg Parker di Apple ha suggerito di farlo in questo modo: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt

2
@Fattie TARGET_OS_SIMULATOR != 0è già nella risposta . È la soluzione data da Daniel. Non è necessario aggiungerlo di nuovo in una variabile libera, è già lì. Se pensi che averlo in una struttura sia male e averlo in una variabile libera è meglio allora pubblica un commento su questo o fai la tua risposta. Grazie.
Eric Aya,

69

Da Xcode 9.3

#if targetEnvironment(simulator)

Swift supporta una nuova condizione della piattaforma targetEnvironment con un singolo simulatore di argomenti valido. La compilazione condizionale del modulo '#if targetEnvironment (simulator)' può ora essere utilizzata per rilevare quando il target di build è un simulatore. Il compilatore Swift tenterà di rilevare, avvisare e suggerire l'uso di targetEnvironment (simulatore) durante la valutazione delle condizioni della piattaforma che sembrano testare indirettamente gli ambienti del simulatore, tramite le condizioni della piattaforma os () e arch () esistenti. (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
    }
}

Prima di iOS 9:

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

Objective-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

2
Il confronto delle stringhe è più fragile rispetto all'utilizzo di costanti definite.
Michael Peterson,

@ P1X3L5 hai ragione! Ma suppongo che questo metodo sia chiamato in modalità debug - non potrebbe essere così solido, ma veloce da aggiungere a un progetto
HotJard

1
@GantMan grazie per la risposta. Ho corretto il codice
HotJard il

@HotJard bello, questo non produce will never be executedavvertimento
Dannie P

59

Swift 4

Ora puoi usare targetEnvironment(simulator)come argomento.

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

Aggiornato per Xcode 9.3


8
Questa dovrebbe ora essere la risposta accettata. Vorrei che ci fosse un modo per proporre una nuova risposta suggerita basata sugli aggiornamenti del sistema operativo / linguaggi di programmazione.
quemeful

4
è un ottimo punto @quemeful - è uno dei pochi difetti di base di SO. Poiché i sistemi informatici cambiano così rapidamente, quasi ogni risposta su SO diventa errata nel tempo .
Fattie,

40

Vorrei chiarire alcune cose qui:

  1. TARGET_OS_SIMULATORnon è impostato nel codice Swift in molti casi; potresti essere accidentalmente importato a causa di un'intestazione ponte ma questo è fragile e non supportato. Inoltre non è nemmeno possibile nei framework. Questo è il motivo per cui alcune persone sono confuse sul fatto che funzioni in Swift.
  2. Consiglio vivamente di non utilizzare l'architettura come sostituto del simulatore.

Per eseguire controlli dinamici:

Il controllo ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nilva benissimo.

Puoi anche simulare il modello sottostante controllando SIMULATOR_MODEL_IDENTIFIERquale restituirà stringhe simili iPhone10,3.

Per eseguire controlli statici:

Xcode 9.2 e precedenti: definisci il tuo flag di compilazione Swift (come mostrato in altre risposte).

Xcode 9.3+ utilizza la nuova condizione targetEnvironment:

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

1
Sembra che tu abbia alcune nuove informazioni interne qui. Molto utile! Nota TARGET_OS_SIMULATOR ha funzionato per un po 'di tempo sia nel codice dell'app che del framework; e funziona anche in Xcode 9.3 b3. Ma immagino che questo sia "accidentale". Tipo di un peccato; perché questo sembra il modo meno confuso. Come fornitore di codice framework che potrebbe essere compilato in Xcode 9.3 o precedenti, sembra che dovremo racchiudere #if targetEnvironment ... in una macro #if swift (> = 4.1) per evitare errori del compilatore. Oppure suppongo che usi .... environment ["SIMULATOR_DEVICE_NAME"]! = Zero. Questo controllo sembra più confuso, IMO.
Daniel,

se si verifica un errore "Condizione piattaforma imprevista ('os', 'arch' o 'swift') previsto utilizzando targetEnvironment (simulatore)
Zaporozhchenko Oleksandr

@Aleksandr è targetEnvironmentatterrato in Xcode 9.3. È necessaria una versione più recente di Xcode.
russbishop

@russbishop buon lavoro chiarendo tutto per l'ultima nuova era - grazie!
Fattie,

Ho inviato una taglia 250, poiché questa risposta sembra aggiungere la maggior parte delle informazioni più recenti - evviva
Fattie

15

Ciò che funziona per me dal momento che Swift 1.0 sta verificando un'architettura diversa da arm:

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

     //simulator
#else 
     //device

#endif

14

Runtime, ma più semplice della maggior parte delle altre soluzioni qui:

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

In alternativa, puoi semplicemente chiamare una funzione helper Objective-C che restituisce un valore booleano che utilizza la macro del preprocessore (specialmente se stai già mescolando nel tuo progetto).

Modifica: non è la soluzione migliore, soprattutto a partire da Xcode 9.3. Vedi la risposta di HotJard


3
Faccio questo, ma ricevo avvisi nella clausola else perché "non verrà mai eseguita". Abbiamo una regola di avviso zero, quindi :-(
EricS

mostrerà un avvertimento ma ha senso, a seconda che tu abbia un simulatore o un dispositivo selezionato per la costruzione, l'avvertimento mostrerà sulla parte che non verrà eseguita, ma sì fastidioso per una politica di avvertimento zero
Fonix

1
Visualizzo avvisi solo quando uso == 0invece di != 0. Usarlo come scritto sopra, anche con un elseblocco dopo, non produce alcun avviso in Swift 4 Xcode versione 9.2 (9C40b)
shim

Inoltre l'ho provato in esecuzione su un obiettivo simulatore e su un dispositivo fisico. Inoltre sembra essere lo stesso in Swift 3.2 (stessa versione di Xcode).
shim

In Xcode 9.3 + Swift 4.1 ho appena notato che ha l'avviso anche con! = 0. Sheesh.
shim

10

Nei sistemi moderni:

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

È facile.


1
Non so perché il primo dovrebbe essere "più corretto" della risposta di Daniel . - Si noti che il secondo è un controllo del tempo di compilazione. Felice anno nuovo!
Martin R,

5

TARGET_IPHONE_SIMULATORè obsoleto in iOS 9. TARGET_OS_SIMULATORè la sostituzione. Inoltre TARGET_OS_EMBEDDEDè disponibile.

Da 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 */ 

1
ho provato TARGET_OS_SIMULATOR ma non funziona o non sono stato riconosciuto dall'Xcode mentre TARGET_IPHONE_SIMULATOR lo fa. Sto costruendo per iOS 8.0 sopra.
CodeOverRide,

Sto guardando le intestazioni di iOS 9. Aggiornerò la mia risposta.
Sitta

5

Spero che questa estensione sia utile.

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

Uso:

if UIDevice.isSimulator {
    print("running on simulator")
}

@ChetanKoli, stavo per rendere il codice molto chiaro, piuttosto che breve, quindi è facile da capire per chiunque. Non sono sicuro di come mi sento riguardo alla tua modifica.
Lucas Chwe,

3

In Xcode 7.2 (e precedenti ma non ho ancora testato quanto prima), puoi impostare un flag di build specifico per la piattaforma "-D TARGET_IPHONE_SIMULATOR" per "Any iOS Simulator".

Cerca nelle impostazioni di compilazione del progetto in "Compilatore Swift - Bandiere clienti", quindi imposta il flag in "Altre bandiere Swift". Puoi impostare un flag specifico per la piattaforma facendo clic sull'icona 'più' quando passi con il mouse sopra una configurazione di build.

Ci sono un paio di vantaggi nel farlo in questo modo: 1) Puoi usare lo stesso test condizionale ("#if TARGET_IPHONE_SIMULATOR") nel tuo codice Swift e Objective-C. 2) È possibile compilare variabili che si applicano solo a ogni build.

Schermata delle impostazioni di compilazione di Xcode



1

Ho usato questo codice di seguito in Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}

1
Faccio questo, ma ricevo avvisi nella clausola else perché "non verrà mai eseguita". Abbiamo una regola di avvertimento zero, quindi grrrr ....
EricS

Mostrerà un avviso ogni volta che si tenta di eseguire con un dispositivo, se si seleziona simulatore per l'esecuzione non verrà visualizzato l'avviso.
Ak_ninan

1
è deprecato
rcmstark,

1

Swift 4:

Attualmente, preferisco usare la classe ProcessInfo per sapere se il dispositivo è un simulatore e quale tipo di dispositivo è in uso:

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

Ma, come sai, simModelCodenon è un codice comodo per capire immediatamente quale tipo di simulatore è stato lanciato, quindi, se necessario, puoi provare a vedere questa altra risposta SO per determinare l'attuale modello di iPhone / dispositivo e avere un più umano stringa leggibile.


1

Ecco un esempio di Xcode 11 Swift basato sulla fantastica risposta di HotJard sopra , questo aggiunge anche un isDeviceBool e usa al SIMULATOR_UDIDposto del nome. Le assegnazioni di variabili vengono eseguite su ogni riga in modo da poterle esaminare più facilmente nel debugger se lo si desidera.

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
    }
}

C'è anche la voce del dizionario DTPlatformNameche dovrebbe contenere simulator.


0

Usa questo codice qui sotto:

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

Funziona per Swift 4eXcode 9.4.1


0

Xcode 11, Swift 5

    #if !targetEnvironment(macCatalyst)
    #if targetEnvironment(simulator)
        true
    #else
        false        
    #endif
    #endif

0

Oltre ad altre risposte.

In Objective-c, assicurati di aver incluso TargetConditionals .

#include <TargetConditionals.h>

prima di usare TARGET_OS_SIMULATOR.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.