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


277

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

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

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

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


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

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

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

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

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

Это просто.


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



1

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

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

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

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

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

#include <TargetConditionals.h>

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.