Как определить, создается ли приложение для устройства или симулятора в Swift

277

В Objective-C мы можем знать, создается ли приложение для устройства или симулятора с использованием макросов:

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

Это макросы времени компиляции, которые недоступны во время выполнения.

Как я могу добиться того же в Swift?

RaffAl
источник
2
Это не то, как обнаружить симулятор или реальное устройство во время выполнения в Objective-C. Это директивы компилятора, которые приводят к различному коду в зависимости от сборки.
rmaddy
Спасибо. Я отредактировал свой вопрос.
RaffAl
9
НАИБОЛЕЕ ГОЛОСОВАННЫЕ ОТВЕТЫ НЕ ЛУЧШИЙ СПОСОБ РЕШЕНИЯ ЭТОЙ ПРОБЛЕМЫ! Ответ mbelsky (в настоящее время очень далеко) является единственным решением, которое приходит без каких-либо ошибок. Даже Грег Паркер из Apple предложил сделать это следующим образом: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
ДАЖЕ В КРЫШКАХ НАВИГАЙТЕСЬ, ЧТОБЫ ПРЕДЛОЖИТЬ, ЧТО НИЧЕГО НЕПРАВИЛЬНО ПРОВЕРЕНО. Предложения инженеров Apple часто являются плохо продуманным мусором или применимы только в определенных ситуациях, так что сам по себе означает меньше, чем ничего.
Толстяк
1
@Fattie: Было бы интересно узнать, почему ни один из приведенных ответов не отвечает вашим потребностям, и на что именно вы надеетесь, предлагая вознаграждение.
Мартин Р

Ответы:

364

Обновление 30/01/19

Несмотря на то, что этот ответ может работать, рекомендуемое решение для статической проверки (как пояснили несколько инженеров Apple) заключается в определении настраиваемого флага компилятора, предназначенного для симуляторов iOS. Подробные инструкции о том, как это сделать, см. В ответе @ mbelsky .

Оригинальный ответ

Если вам нужна статическая проверка (например, не во время выполнения if / else), вы не можете обнаружить симулятор напрямую, но вы можете обнаружить iOS на настольной архитектуре, как показано ниже

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

После версии Swift 4.1

Последнее использование, теперь непосредственно для всех в одном условии, для всех типов тренажеров необходимо применять только одно условие -

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

Для получения дополнительной информации вы можете проверить предложение Swift SE-0190


Для старой версии -

Ясно, что это ложно на устройстве, но оно возвращает истину для симулятора iOS, как указано в документации :

Конфигурация сборки arch (i386) возвращает true, когда код компилируется для 32-битного симулятора iOS.

Если вы разрабатываете для симулятора, кроме iOS, вы можете просто изменить osпараметр: например,

Определить симулятор watchOS

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

Определить симулятор tvOS

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

Или даже обнаружить любой симулятор

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

Если вместо этого вы согласны с проверкой во время выполнения, вы можете проверить TARGET_OS_SIMULATORпеременную (или TARGET_IPHONE_SIMULATORв iOS 8 и ниже), что верно на симуляторе.

Обратите внимание, что это отличается и немного более ограничено, чем использование флага препроцессора. Например, вы не сможете использовать его там, где if/elseсинтаксически недопустим (например, за пределами области действия функций).

Скажем, например, что вы хотите иметь разные импорты на устройстве и на симуляторе. Это невозможно при динамической проверке, тогда как при статической проверке это тривиально.

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

Кроме того, поскольку флаг заменяется на 0или или 1препроцессором swift, если вы непосредственно используете его в if/elseвыражении, компилятор выдаст предупреждение о недоступном коде.

Чтобы обойти это предупреждение, см. Один из других ответов.

Габриэле Петронелла
источник
1
Больше читать здесь . И чтобы быть еще более ограничительным, вы могли бы использовать arch(i386) && os(iOS).
ahruss
1
Это не сработало для меня. Я должен был проверить и для i386 и для x86_64
akaru
3
ЭТОТ ОТВЕТ НЕ ЛУЧШИЙ СПОСОБ РЕШЕНИЯ ЭТОЙ ПРОБЛЕМЫ! Ответ mbelsky (в настоящее время очень далеко) является единственным решением, которое приходит без каких-либо ошибок. Даже Грег Паркер из Apple предложил сделать это так: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@russbishop это оказалось полезным советом для сотен людей, компенсирующих отсутствующий API. Вместо того, чтобы угонять ответ, подписав комментарий сверху, просто общайтесь. Я обновил ответ, чтобы уточнить, что это больше не актуальное решение, и я предоставил ссылку на ту, которая выглядит более корректно.
Габриэле Петронелла,
9
В Swift 4.1 вы сможете сказать #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/proposals/… )
Хэмиш
172

УСТАРЕЛО ДЛЯ SWIFT 4.1. Используйте #if targetEnvironment(simulator)вместо этого. Источник

Для обнаружения симулятора в Swift вы можете использовать конфигурацию сборки:

  • Определите эту конфигурацию -D IOS_SIMULATOR в Swift Compiler - Пользовательские флаги> Другие флаги Swift
  • Выберите любой iOS Simulator SDK в этом раскрывающемся списке.Раскрывающийся список

Теперь вы можете использовать это утверждение для обнаружения симулятора:

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

Также вы можете расширить класс UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator
mbelsky
источник
8
Это должен быть лучший ответ! Даже Грег Паркер из Apple предложил такой способ: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
1
обновление использования для swift 3: UIDevice.current.isSimulator
tylernol
1
Могу я спросить, почему, если я добавлю это в Release, это не сработает?
Уильям Ху
3
Это единственный правильный ответ. Вы также можете установить это в xcconfigфайлах с помощью OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDи OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORпереопределить для симулятора.
русский епископ
1
На Xcode 9.2 этот ответ не удавался компилировать некоторое время. Удаление «-» перед «D» решило проблему для меня.
Блейк
160

Обновленная информация от 20 февраля 2018 г.

Похоже, у @russbishop есть авторитетный ответ, который делает этот ответ «неправильным», хотя он, казалось, работал долгое время.

Определить, создается ли приложение для устройства или симулятора в Swift

Предыдущий ответ

Основываясь на ответах @ WZW и @ Pang, я создал простую структуру утилит. Это решение позволяет избежать предупреждения, создаваемого ответом @ WZW.

import Foundation

struct Platform {

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

}

Пример использования:

if Platform.isSimulator {
    print("Running on Simulator")
}
Даниил
источник
10
Гораздо лучшее решение, чем принято. Действительно, если когда-нибудь (даже если это очень маловероятно) Apple решит использовать i386 или x85_64 на устройствах iOS, принятый ответ не сработает ... или даже если настольные компьютеры получат новый процесс!
Фризлаб
2
Подтвердили, что это прекрасно работает на Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... то же самое, упрощенно. +1 спасибо
Дэн Розенстарк
1
@daniel Это хорошо работает, и это на самом деле проще, чем мое решение. Однако стоит отметить, что он более ограничен, чем фактический шаг препроцессора. Если вам нужно, чтобы какая-то часть кода не была включена в цель (например, вы хотите выбирать между двумя импортами во время компиляции), вам нужно использовать статическую проверку. Я отредактировал свой ответ, чтобы подчеркнуть эту разницу.
Габриэле Петронелла,
ЭТОТ ОТВЕТ НЕ ЛУЧШИЙ СПОСОБ РЕШЕНИЯ ЭТОЙ ПРОБЛЕМЫ! Ответ mbelsky (в настоящее время очень далеко) является единственным решением, которое приходит без каких-либо ошибок. Даже Грег Паркер из Apple предложил сделать это так: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
jan.vogt
2
@Fattie TARGET_OS_SIMULATOR != 0это уже в ответе . Это решение, данное Дэниелом. Нет необходимости добавлять его снова в свободную переменную, он уже есть. Если вы считаете, что иметь его в структуре плохо, а лучше иметь свободную переменную, лучше оставьте комментарий или сделайте свой ответ. Спасибо.
Эрик Ая
69

От Xcode 9.3

#if targetEnvironment(simulator)

Swift поддерживает новое условие платформы targetEnvironment с одним действительным имитатором аргумента. Условная компиляция формы «#if targetEnvironment (simulator)» теперь может использоваться для определения, когда целью сборки является симулятор. Компилятор Swift попытается обнаружить, предупредить и предложить использовать targetEnvironment (симулятор) при оценке условий платформы, которые, по-видимому, косвенно тестируют среды симулятора через существующие условия платформы os () и arch (). (SE-0190)

iOS 9+:

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

Свифт 3:

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

До 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
HotJard
источник
2
Сравнение строк является более хрупким, чем использование определенных констант.
Майкл Петерсон
@ P1X3L5 ты прав! Но я предполагаю, что этот метод вызывается в режиме отладки - он не может быть настолько надежным, но его можно быстро добавить в проект
HotJard
1
@GantMan спасибо за ответ. Я исправил код
HotJard
@HotJard хорошо, этот не выдает will never be executedпредупреждение
Dannie P
59

Swift 4

Теперь вы можете использовать targetEnvironment(simulator)в качестве аргумента.

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

Обновлено для Xcode 9.3

Мэтт Свифт
источник
8
Теперь это должен быть принятый ответ. Хотелось бы, чтобы на SO был способ предложить новый предложенный ответ на основе обновлений для OS / языков программирования.
унылый
4
это замечательный момент @quemeful - это один из немногих основных недостатков SO. Поскольку вычислительные системы меняются так быстро, почти каждый ответ на SO становится неверным со временем .
Толстяк
40

Позвольте мне уточнить некоторые вещи здесь:

  1. TARGET_OS_SIMULATORво многих случаях не установлен в коде Swift; Вы можете случайно импортировать его из-за связующего заголовка, но это хрупко и не поддерживается. Это также невозможно даже в рамках. Вот почему некоторые люди не понимают, работает ли это в Swift.
  2. Я настоятельно рекомендую не использовать архитектуру вместо симулятора.

Для выполнения динамических проверок:

проверка ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil в порядке.

Вы также можете получить базовую модель, SIMULATOR_MODEL_IDENTIFIERкоторая будет смоделирована, проверив, какие строки будут возвращаться как iPhone10,3.

Для выполнения статических проверок:

Xcode 9.2 и ранее: определите свой собственный флаг компиляции Swift (как показано в других ответах).

Xcode 9.3+ использует новое условие targetEnvironment:

#if targetEnvironment(simulator)
    // for sim only
#else
    // for device
#endif
russbishop
источник
1
Похоже, у вас есть новая внутренняя информация здесь. Очень полезно! Обратите внимание, что TARGET_OS_SIMULATOR довольно долго работал как в коде приложения, так и в фреймворке; и это также работает в Xcode 9.3 b3. Но, я думаю, это "случайно". Вид облома; потому что это кажется наименее хакерским способом. Как поставщик кода инфраструктуры, который может быть скомпилирован в Xcode 9.3 или более ранней версии, похоже, что нам нужно будет обернуть #if targetEnvironment ... в макрос #if swift (> = 4.1), чтобы избежать ошибок компилятора. Или я предполагаю использовать .... среду ["SIMULATOR_DEVICE_NAME"]! = Ноль. Эта проверка кажется более хакерской, ИМО.
Даниил
if с ошибкой «Неожиданное состояние платформы (ожидаемое os, arch или swift») при использовании targetEnvironment (симулятор)
Запорожченко Александр
@ Александр targetEnvironmentприземлился в Xcode 9.3. Вам нужна более новая версия Xcode.
русский епископ
@russbishop хорошая работа, проясняющая это для последней новой эры - спасибо!
Толстяк
Я послал награду за 250, так как этот ответ, кажется, добавляет самую новую информацию - ура
Fattie
15

Что работает для меня, так как Swift 1.0 проверяет архитектуру, отличную от arm:

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

     //simulator
#else 
     //device

#endif
akaru
источник
14

Время выполнения, но проще, чем большинство других решений здесь:

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

Кроме того, вы можете просто вызвать вспомогательную функцию Objective C, которая возвращает логическое значение, использующее макрос препроцессора (особенно, если вы уже микшируете в своем проекте).

Изменить: не лучшее решение, особенно в Xcode 9.3. Посмотреть ответ HotJard

шайба
источник
3
Я делаю это, но получаю предупреждения в предложении else, потому что оно «никогда не будет выполнено». У нас есть правило нулевого предупреждения, так что :-(
EricS
он будет показывать предупреждение, но имеет смысл, в зависимости от того, выбран ли для построения симулятор или устройство, предупреждение будет отображаться на части, которая не будет выполнена, но да, раздражает политика нулевого предупреждения
Fonix
1
Видеть только предупреждения, когда я использую == 0вместо != 0. Используя это , как написано выше, даже с elseблоком после, не производит каких - либо предупреждений в Swift 4 Xcode версии 9.2 (9C40b)
регулировочные
Также я протестировал его на симуляторе, а также на физическом устройстве. Также похоже, что то же самое в Swift 3.2 (та же версия XCode).
прокладка
В Xcode 9.3 + Swift 4.1 я только что заметил, что у него есть предупреждение даже при! = 0. Sheesh.
Шим
10

В современных системах:

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

Это просто.

Fattie
источник
1
Не уверен, почему первый должен быть «более правильным», чем ответ Даниэля . - Обратите внимание , что вторая одна является компилированием проверки времени. С Новым Годом!
Мартин Р
5

TARGET_IPHONE_SIMULATORустарела в iOS 9. TARGET_OS_SIMULATORэто замена. ТакжеTARGET_OS_EMBEDDED имеется в наличии.

Из 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
я попробовал TARGET_OS_SIMULATOR, но Xcode не работает или не распознается, в то время как TARGET_IPHONE_SIMULATOR работает. Я строю для iOS 8.0 выше.
CodeOverRide
Я смотрю на заголовки iOS 9. Я обновлю свой ответ.
Поползень
5

Я надеюсь, что это расширение пригодится.

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

Использование:

if UIDevice.isSimulator {
    print("running on simulator")
}
Лукас Чве
источник
@ChetanKoli, я собирался сделать код очень понятным, а не коротким, чтобы его было легко понять любому. Не уверен, что я чувствую по поводу вашего редактирования.
Лукас Чве
3

В Xcode 7.2 (и ранее, но я не проверял, сколько раньше) вы можете установить специфичный для платформы флаг сборки "-D TARGET_IPHONE_SIMULATOR" для "Любого симулятора iOS".

Посмотрите в настройках сборки проекта в «Swift Compiler - Customer Flags», а затем установите флаг в «Другие флаги Swift». Вы можете установить флаг для конкретной платформы, щелкнув значок «плюс» при наведении указателя мыши на конфигурацию сборки.

Есть несколько преимуществ сделать это следующим образом: 1) Вы можете использовать один и тот же условный тест ("#if TARGET_IPHONE_SIMULATOR") в вашем коде Swift и Objective-C. 2) Вы можете компилировать переменные, которые применяются только к каждой сборке.

Скриншот настроек сборки XCode

xgerrit
источник
1

Я использовал этот код ниже в Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}
ak_ninan
источник
1
Я делаю это, но получаю предупреждения в предложении else, потому что оно «никогда не будет выполнено». У нас есть правило нулевого предупреждения, так что grrrr ....
EricS
Он будет отображать предупреждение всякий раз, когда вы пытаетесь запустить устройство, если вы выбрали симулятор для запуска, он не будет отображать предупреждение.
ak_ninan
1
устарело
rcmstark
1

Свифт 4:

В настоящее время я предпочитаю использовать класс ProcessInfo, чтобы узнать, является ли устройство симулятором и какое устройство используется:

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

Но, как вы знаете, simModelCodeнеудобный код, чтобы сразу понять, какой тип симулятора был запущен, поэтому, если вам нужно, вы можете попытаться увидеть этот другой SO- ответ, чтобы определить текущую модель iPhone / устройства и иметь более человечный читаемая строка.

Алессандро Орнано
источник
1

Вот пример Xcode 11 Swift, основанный на удивительном ответе HotJard выше , он также добавляет isDeviceBool и использует SIMULATOR_UDIDвместо имени. Переменные присваиваются в каждой строке, чтобы вы могли легче просматривать их в отладчике, если захотите.

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

Там также есть словарь, запись DTPlatformNameкоторого должна содержать simulator.

Алекс Заватоне
источник
0

Используйте этот код ниже:

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

Работает для Swift 4иXcode 9.4.1

Гарольдо Гондим
источник
0

Xcode 11, Swift 5

    #if !targetEnvironment(macCatalyst)
    #if targetEnvironment(simulator)
        true
    #else
        false        
    #endif
    #endif
UnchartedWorks
источник
0

В дополнение к другим ответам.

В Objective-c, просто убедитесь, что вы включили TargetConditionals .

#include <TargetConditionals.h>

перед использованием TARGET_OS_SIMULATOR.

М. Али
источник