Cómo detectar si la aplicación se está creando para dispositivo o simulador en Swift

277

En Objective-C podemos saber si se está creando una aplicación para dispositivo o simulador usando macros:

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

Estas son macros de tiempo de compilación y no están disponibles en tiempo de ejecución.

¿Cómo puedo lograr lo mismo en Swift?

RaffAl
fuente
2
Esa no es la forma de detectar el simulador o un dispositivo real en tiempo de ejecución en Objective-C. Esas son directivas del compilador que dan como resultado un código diferente según la compilación.
rmaddy
Gracias. Edité mi pregunta.
RaffAl
9
¡LAS RESPUESTAS MÁS ALTAS VOTADAS NO SON LA MEJOR MANERA DE RESOLVER ESTE PROBLEMA! La respuesta de mbelsky (actualmente muy lejos) es la única solución que viene sin ningún inconveniente. Incluso Greg Parker de Apple sugirió hacerlo de esa manera: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
Incluso en mayúsculas, es ingenuo sugerir que hay algo mal con un cheque de tiempo de ejecución. Las sugerencias de los ingenieros de Apple a menudo son basura mal pensada, o solo se aplican en ciertas situaciones, por lo que en sí mismo significa menos que nada.
Fattie
1
@Fattie: Sería interesante saber por qué ninguna de las respuestas dadas satisface sus necesidades y qué es exactamente lo que espera al ofrecer la recompensa.
Martin R

Respuestas:

364

Actualización 30/01/19

Si bien esta respuesta puede funcionar, la solución recomendada para una comprobación estática (como lo aclararon varios ingenieros de Apple) es definir un indicador de compilador personalizado dirigido a los simuladores de iOS. Para obtener instrucciones detalladas sobre cómo hacerlo, consulte la respuesta de @ mbelsky .

Respuesta original

Si necesita una comprobación estática (por ejemplo, no es un tiempo de ejecución si / no) no puede detectar el simulador directamente, pero puede detectar iOS en una arquitectura de escritorio de la siguiente manera

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

Después de la versión Swift 4.1

El último uso, ahora directamente para todo en una condición para todos los tipos de simuladores necesita aplicar solo una condición:

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

Para más aclaraciones, puede consultar la propuesta de Swift SE-0190


Para versión anterior -

Claramente, esto es falso en un dispositivo, pero devuelve verdadero para el Simulador de iOS, como se especifica en la documentación :

La configuración de compilación de arch (i386) vuelve verdadera cuando se compila el código para el simulador de iOS de 32 bits.

Si está desarrollando un simulador que no sea iOS, simplemente puede variar el osparámetro: por ejemplo

Detecta el simulador de watchOS

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

Detecta el simulador de tvOS

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

O, incluso, detectar cualquier simulador

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

Si, en cambio, está de acuerdo con una verificación de tiempo de ejecución, puede inspeccionar la TARGET_OS_SIMULATORvariable (o TARGET_IPHONE_SIMULATORen iOS 8 y versiones posteriores), lo cual es cierto en un simulador.

Tenga en cuenta que esto es diferente y un poco más limitado que usar un indicador de preprocesador. Por ejemplo, no podrá usarlo en el lugar donde a if/elsees sintácticamente inválido (por ejemplo, fuera de los ámbitos de funciones).

Digamos, por ejemplo, que desea tener diferentes importaciones en el dispositivo y en el simulador. Esto es imposible con una verificación dinámica, mientras que es trivial con una verificación estática.

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

Además, dado que el indicador es reemplazado por a 0o a 1por el preprocesador rápido, si lo usa directamente en una if/elseexpresión, el compilador generará una advertencia sobre el código inalcanzable.

Para evitar esta advertencia, consulte una de las otras respuestas.

Gabriele Petronella
fuente
1
Más lectura aquí . Y para ser aún más restrictivo, podrías usarlo arch(i386) && os(iOS).
ahruss
1
Esto no funcionó para mí. Tuve que verificar tanto para i386 como para x86_64
akaru
3
¡ESTA RESPUESTA NO ES LA MEJOR MANERA DE RESOLVER ESTE PROBLEMA! La respuesta de mbelsky (actualmente muy lejos) es la única solución que viene sin ningún inconveniente. Incluso Greg Parker de Apple sugirió hacerlo de esa manera: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@russbishop esto resultó ser un consejo útil para cientos de personas hasta ahora, compensando la falta de API. En lugar de secuestrar la respuesta firmando un comentario en la parte superior, solo comunícate. Actualicé la respuesta para aclarar que esto ya no es una solución actualizada y he proporcionado un enlace a la que parece más correcta.
Gabriele Petronella
9
En Swift 4.1, podrás decir #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/proposals/… )
Hamish
172

ACTUALIZADO PARA SWIFT 4.1. Usar en su #if targetEnvironment(simulator)lugar. Fuente

Para detectar el simulador en Swift, puede usar la configuración de compilación:

  • Defina esta configuración -D IOS_SIMULATOR en el compilador Swift - Banderas personalizadas> Otras banderas Swift
  • Seleccione Any iOS Simulator SDK en este menú desplegableLa lista desplegable

Ahora podría usar esta declaración para detectar el simulador:

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

También podría extender la clase UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator
mbelsky
fuente
8
¡Esta debería ser la mejor respuesta! Incluso Greg Parker de Apple sugirió de esa manera: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
actualización de uso para swift 3: UIDevice.current.isSimulator
tylernol
1
¿Puedo preguntar por qué si agrego esto en Release, esto no funciona?
William Hu
3
Esta es la única respuesta correcta. También puede configurar esto en xcconfigarchivos usando OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDy OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORpara anular el Simulador.
obispo ruso
1
En Xcode 9.2, esta respuesta no pudo compilarse algunas veces. Eliminar el "-" antes de la "D" resolvió el problema por mí.
Blake
160

Información actualizada al 20 de febrero de 2018

Parece que @russbishop tiene una respuesta autorizada que hace que esta respuesta sea "incorrecta", a pesar de que pareció funcionar durante mucho tiempo.

Detecta si la aplicación se está creando para dispositivo o simulador en Swift

Respuesta anterior

Basado en la respuesta de @ WZW y los comentarios de @ Pang, creé una estructura de utilidad simple. Esta solución evita la advertencia producida por la respuesta de @ WZW.

import Foundation

struct Platform {

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

}

Ejemplo de uso:

if Platform.isSimulator {
    print("Running on Simulator")
}
Daniel
fuente
10
Una solución mucho mejor que la aceptada. De hecho, si algún día (aunque es muy poco probable) Apple decida usar i386 o x85_64 en dispositivos iOS, la respuesta aceptada no funcionará ... ¡o incluso si las computadoras de escritorio obtienen un nuevo proceso!
Frizlab
2
Confirmó que esto funciona perfectamente en Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... lo mismo, simplificado. +1 gracias
Dan Rosenstark
1
@daniel Esto funciona bien y en realidad es más sencillo que mi solución. Sin embargo, vale la pena señalar que es más limitado que un paso de preprocesador real. Si necesita que una parte del código no se incluya en el destino (por ejemplo, si desea elegir entre dos importaciones en el momento de la compilación), debe usar una comprobación estática. He editado mi respuesta para resaltar esta diferencia.
Gabriele Petronella
¡ESTA RESPUESTA NO ES LA MEJOR MANERA DE RESOLVER ESTE PROBLEMA! La respuesta de mbelsky (actualmente muy lejos) es la única solución que viene sin ningún inconveniente. Incluso Greg Parker de Apple sugirió hacerlo de esa manera: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@Fattie yaTARGET_OS_SIMULATOR != 0 está en la respuesta . Es la solución dada por Daniel. No es necesario agregarlo nuevamente en una variable libre, ya está allí. Si cree que tenerlo en una estructura es malo y tenerlo en una variable libre es mejor, publique un comentario al respecto o haga su propia respuesta. Gracias.
Eric Aya
69

Desde Xcode 9.3

#if targetEnvironment(simulator)

Swift admite una nueva condición de plataforma targetEnvironment con un único simulador de argumento válido. La compilación condicional de la forma '#if targetEnvironment (simulator)' ahora se puede usar para detectar cuándo el target de compilación es un simulador. El compilador Swift intentará detectar, advertir y sugerir el uso de TargetEnvironment (simulador) al evaluar las condiciones de la plataforma que parecen estar probando los entornos del simulador indirectamente, a través de las condiciones existentes de la plataforma os () y arch (). (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
    }
}

Antes de iOS 9:

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

C objetivo:

@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
fuente
2
Comparar cadenas es más frágil que usar constantes definidas.
Michael Peterson
@ P1X3L5 tienes razón! Pero supongo que este método se llama en modo de depuración; no podría ser tan sólido, pero rápido de agregar a un proyecto
HotJard
1
@GantMan gracias por la respuesta. He arreglado el código
HotJard
@HotJard agradable, este no produce will never be executedadvertencia
Dannie P
59

Swift 4

Ahora puede usarlo targetEnvironment(simulator)como argumento.

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

Actualizado para Xcode 9.3

Matt Swift
fuente
8
Esta debería ser ahora la respuesta aceptada. Ojalá hubiera una manera en SO de proponer una nueva respuesta sugerida basada en actualizaciones de SO / lenguajes de programación.
quemeful
44
Es un gran punto @quemeful: es uno de los pocos fallos básicos de SO. Dado que los sistemas informáticos cambian tan rápidamente, casi todas las respuestas en SO se vuelven incorrectas con el tiempo .
Fattie
40

Permítanme aclarar algunas cosas aquí:

  1. TARGET_OS_SIMULATORno se establece en el código Swift en muchos casos; es posible que lo haya importado accidentalmente debido a un encabezado de puente, pero esto es frágil y no es compatible. Tampoco es posible en frameworks. Es por eso que algunas personas están confundidas acerca de si esto funciona en Swift.
  2. Recomiendo encarecidamente no utilizar la arquitectura como sustituto del simulador.

Para realizar comprobaciones dinámicas:

La comprobación ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nilestá perfectamente bien.

También puede obtener el modelo subyacente que se está simulando al verificar SIMULATOR_MODEL_IDENTIFIERqué devolverá cadenas como iPhone10,3.

Para realizar comprobaciones estáticas:

Xcode 9.2 y anteriores: defina su propio indicador de compilación Swift (como se muestra en otras respuestas).

Xcode 9.3+ usa la nueva condición targetEnvironment:

#if targetEnvironment(simulator)
    // for sim only
#else
    // for device
#endif
obispo ruso
fuente
1
Parece que tienes alguna información nueva aquí. ¡Muy útil! Tenga en cuenta que TARGET_OS_SIMULATOR funcionó durante bastante tiempo tanto en la aplicación como en el código marco; y también funciona en Xcode 9.3 b3. Pero, supongo que esto es "accidental". Una especie de fastidio; porque esta parece ser la forma menos hacky. Como proveedor de código marco que podría compilarse en Xcode 9.3 o anterior, parece que tendremos que ajustar #if targetEnvironment ... en una macro #if swift (> = 4.1) para evitar errores del compilador. O supongo que usa ... entorno ["SIMULATOR_DEVICE_NAME"]! = Nulo. Esta verificación parece más hacky, en mi opinión.
Daniel
si tiene el error "Condición de plataforma inesperada ('os' esperado, 'arco' o 'rápido')" al usar TargetEnvironment (simulador)
Zaporozhchenko Oleksandr
@Aleksandr targetEnvironmentaterrizó en Xcode 9.3. Necesita una versión más nueva de Xcode.
russbishop
@russbishop buen trabajo aclarando esto para la última era nueva - ¡gracias!
Fattie
Envié una recompensa de 250, ya que esta respuesta parece agregar la mayor y más nueva información - salud
Fattie
15

Lo que funciona para mí desde Swift 1.0 es buscar una arquitectura que no sea arm:

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

     //simulator
#else 
     //device

#endif
akaru
fuente
14

Tiempo de ejecución, pero más simple que la mayoría de las otras soluciones aquí:

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

Alternativamente, puede llamar a una función auxiliar Objective-C que devuelve un valor booleano que usa la macro del preprocesador (especialmente si ya está mezclando en su proyecto).

Editar: no es la mejor solución, especialmente a partir de Xcode 9.3. Ver la respuesta de HotJard

calce
fuente
3
Hago esto pero recibo advertencias en la cláusula else porque "nunca se ejecutará". Tenemos una regla de advertencia cero, así que :-(
EricS
mostrará una advertencia pero tiene sentido, dependiendo de si tiene un simulador o dispositivo seleccionado para la construcción, la advertencia se mostrará en la parte que no se ejecutará, pero sí molesto para una política de advertencia cero
Fonix
1
Solo veo advertencias cuando uso en == 0lugar de != 0. Usarlo como está escrito arriba, incluso con un elsebloque después, no produce ninguna advertencia en Swift 4 Xcode Versión 9.2 (9C40b)
shim
También lo probé ejecutándose en un simulador de destino, así como en un dispositivo físico. También parece ser lo mismo en Swift 3.2 (misma versión de Xcode).
cuña
En Xcode 9.3 + Swift 4.1, acabo de notar que tiene la advertencia incluso con! = 0. Sheesh
cuña
10

En sistemas modernos:

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

Es muy fácil.

Fattie
fuente
1
No estoy seguro de por qué el primero debería ser "más correcto" que la respuesta de Daniel . - Tenga en cuenta que el segundo es una verificación de tiempo de compilación. ¡Feliz año nuevo!
Martin R
5

TARGET_IPHONE_SIMULATORestá en desuso en iOS 9. TARGET_OS_SIMULATORes el reemplazo. También TARGET_OS_EMBEDDEDestá disponible

Desde 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 */ 
Trepatroncos
fuente
1
probé TARGET_OS_SIMULATOR pero no funciona ni me reconoce el Xcode mientras que TARGET_IPHONE_SIMULATOR sí. Estoy construyendo para iOS 8.0 arriba.
CodeOverRide
Estoy mirando los encabezados de iOS 9. Actualizaré mi respuesta.
Trepatroncos
5

Espero que esta extensión sea útil.

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

Uso:

if UIDevice.isSimulator {
    print("running on simulator")
}
Lucas Chwe
fuente
@ChetanKoli, iba a dejar el código muy claro, en lugar de breve, por lo que es fácil de entender para cualquiera. No estoy seguro de cómo me siento acerca de tu edición.
Lucas Chwe
3

En Xcode 7.2 (y anteriores, pero no he probado cuánto antes), puede establecer un indicador de compilación específico de plataforma "-D TARGET_IPHONE_SIMULATOR" para "Any iOS Simulator".

Busque en la configuración de compilación del proyecto en "Compilador Swift - Indicadores del cliente" y luego establezca el indicador en "Otros indicadores Swift". Puede establecer un indicador específico de plataforma haciendo clic en el ícono 'más' cuando pasa el mouse sobre una configuración de compilación.

Hay un par de ventajas de hacerlo de esta manera: 1) Puede usar la misma prueba condicional ("#if TARGET_IPHONE_SIMULATOR") en su código Swift y Objective-C. 2) Puede compilar variables que solo se aplican a cada compilación.

Captura de pantalla de configuración de compilación de Xcode

xgerrit
fuente
1

Usé este código a continuación en Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}
ak_ninan
fuente
1
Hago esto pero recibo advertencias en la cláusula else porque "nunca se ejecutará". Tenemos una regla de advertencia cero, así que grrrr ...
EricS
Mostrará una advertencia cada vez que intente ejecutar con un dispositivo; si se selecciona el simulador para ejecutar, no mostrará la advertencia.
ak_ninan
1
está en desuso
rcmstark
1

Swift 4:

Actualmente, prefiero usar la clase ProcessInfo para saber si el dispositivo es un simulador y qué tipo de dispositivo está en uso:

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

Pero, como saben, simModelCodeno es un código cómodo para comprender de inmediato qué tipo de simulador se lanzó, por lo que, si es necesario, puede intentar ver esta otra respuesta SO para determinar el modelo actual de iPhone / dispositivo y tener un humano más cadena legible.

Alessandro Ornano
fuente
1

Aquí hay un ejemplo de Xcode 11 Swift basado en la increíble respuesta de HotJard anterior , esto también agrega un isDeviceBool y usa en SIMULATOR_UDIDlugar de nombre. Las asignaciones variables se realizan en cada línea para que pueda examinarlas más fácilmente en el depurador si lo desea.

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

También existe la entrada del diccionario DTPlatformNameque debe contener simulator.

Alex Zavatone
fuente
0

Use este código a continuación:

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

Trabaja para Swift 4yXcode 9.4.1

Haroldo Gondim
fuente
0

Xcode 11, Swift 5

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

Además de otras respuestas.

En Objective-c, solo asegúrese de incluir TargetConditionals .

#include <TargetConditionals.h>

antes de usar TARGET_OS_SIMULATOR.

M. Ali
fuente