Як визначити, чи створено додаток для пристрою чи симулятора в Swift


277

У Objective-C ми можемо знати, чи будується програма для пристрою чи симулятора за допомогою макросів:

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

Це макроси часу компіляції та недоступні під час виконання.

Як я можу досягти того ж у Свіфті?


2
Це не спосіб виявити тренажер або реальний пристрій під час виконання в Objective-C. Це директиви компілятора, які призводять до різного коду залежно від збірки.
rmaddy

Дякую. Я відредагував своє запитання.
RaffAl

9
Найвищі голосовані відповіді - це не найкращий спосіб вирішити цю проблему! Відповідь mbelsky (на даний момент дуже далеко вниз) - єдине рішення, яке виходить без жодних підводних каменів. Навіть Грег Паркер з Apple запропонував зробити це так: list.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
січень.вогт

1
ВІД КАПІВ, НАЙБІЛЬШЕ НАПРАВИТЬСЯ, ЩО Є НЕЩО НЕБЕЗПЕЧЕНО З РЕЧЕНОМ. Пропозиції інженерів Apple часто є погано продуманим сміттям або застосовуються лише в певних ситуаціях, так що саме по собі означає менше, ніж нічого.
Fattie

1
@Fattie: Цікаво було б знати, чому жоден із поданих відповідей не задовольняє ваших потреб, і на що ви точно сподіваєтесь, пропонуючи виграш.
Мартін Р

Відповіді:


363

Оновлення 30.01.19

Хоча ця відповідь може працювати, рекомендованим рішенням для статичної перевірки (як уточнили декілька інженерів Apple) є визначення спеціального прапора компілятора, орієнтованого на iOS Simulators. Детальні вказівки, як це зробити, дивіться у відповіді @ mbelsky .

Оригінальна відповідь

Якщо вам потрібна статична перевірка (наприклад, не час виконання, якщо / ще), ви не зможете виявити тренажер безпосередньо, але ви можете виявити 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 Simulator, як зазначено в документації :

Конфігурація побудови арки (i386) повертає істину, коли код компілюється для 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

Крім того, оскільки прапор швидкого препроцесора замінено на a 0або a 1, якщо ви безпосередньо використовуєте його у if/elseвиразі, компілятор підніме попередження про недоступний код.

Щоб вирішити це попередження, дивіться одну з інших відповідей.


1
Більше читання тут . А щоб бути ще більш обмежуючим, ви могли б скористатися arch(i386) && os(iOS).
ahruss

1
Це для мене не вийшло. Мені довелося перевірити як i386, так і x86_64
akaru

3
ЦЕ ВІДПОВІДЬ НЕ найкращий спосіб вирішити цю проблему! Відповідь mbelsky (на даний момент дуже далеко вниз) - єдине рішення, яке виходить без жодних підводних каменів. Навіть Грег Паркер з Apple запропонував зробити це так: list.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
  • Виберіть Будь-який SDK для симулятора iOS у цьому спадному менюВипадаючий список

Тепер ви можете використовувати цю заяву для виявлення симулятора:

#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 запропонував такий спосіб: list.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…
січень

1
оновлення використання для swift 3: UIDevice.current.isSimulator
tylernol

1
Чи можу я запитати, чому якщо я додаю це у розділі Випуск, це не працює?
Вільям Ху

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 та коментарів @ Панга я створив просту структуру утиліти. Це рішення дозволяє уникнути попередження, отриманого відповіддю @ 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, прийнята відповідь не буде працювати ... або навіть якщо настільні комп'ютери отримають нову процедуру!
Frizlab

2
Підтвердив, що це ідеально працює на Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... те саме, спрощено. +1 спасибі
Dan Rosenstark

1
@daniel Це добре працює, і це насправді більш просто, ніж моє рішення. Однак варто зазначити, що це більш обмежений, ніж фактичний крок препроцесора. Якщо вам потрібна частина частини коду, щоб вона не була включена в ціль (наприклад, ви хочете вибрати між двома імпортами під час компіляції), вам доведеться використовувати статичну перевірку. Я відредагував свою відповідь, щоб виділити цю різницю.
Габріеле Петронелла

ЦЕ ВІДПОВІДЬ НЕ найкращий спосіб вирішити цю проблему! Відповідь mbelsky (на даний момент дуже далеко вниз) - єдине рішення, яке виходить без жодних підводних каменів. Навіть Грег Паркер з Apple запропонував зробити це так: list.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 підтримує нову умову платформи targetEl Environment із єдиним дійсним симулятором аргументів. Умовна компіляція форми "#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"
    }
}

Завдання-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

Швидкий 4

Тепер ви можете використовувати targetEnvironment(simulator)як аргумент.

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

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


8
Тепер це має бути прийнятою відповіддю. Мені б хотілося, щоб на SO було запропоновано нову запропоновану відповідь на основі оновлень ОС / мов програмування.
пам’ятний

4
це чудовий момент @quemeful - це один з небагатьох основних недоліків SO. Оскільки обчислювальні системи змінюються настільки швидко, майже кожна відповідь на SO з часом стає неправильною .
Fattie

40

Дозвольте мені уточнити деякі речі тут:

  1. TARGET_OS_SIMULATORне встановлюється в коді Swift у багатьох випадках; Ви можете випадково отримати його імпортованим через мостиковий заголовок, але це крихко і не підтримується. Це також неможливо навіть в рамках. Ось чому деякі люди плутаються у тому, чи працює це у Свіфті.
  2. Я настійно не рекомендую використовувати архітектуру як заміну симулятора.

Щоб виконати динамічні перевірки:

Перевірка ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil- це прекрасно.

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

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

Xcode 9.2 та новіших версій: визначте власний прапор компіляції Swift (як показано в інших відповідях).

Xcode 9.3+ використовуйте нову умову навколишнього середовища:

#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"]! = Нуль. Ця перевірка здається більш хитрою, IMO.
Даніель

якщо помилка "Несподівана умова платформи (очікувана" ос "," арка "або" стрімка ")" за допомогою targetEl Environment (тренажер)
Запорожченко Олександр

@Aleksandr targetEnvironmentприземлився в Xcode 9.3. Вам потрібна нова версія Xcode.
єпископ

@russbishop добре працюю з очищенням цього за останню нову еру - дякую!
Fattie

Я надіслав 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
Я роблю це, але отримую попередження в іншому пункті, оскільки воно "ніколи не буде виконано". У нас є нульове правило попередження, тому :-(
EricS

воно покаже попередження, але це має сенс, залежно від того, якщо у вас вибрано тренажер чи пристрій для будівництва, попередження з’явиться на частині, яка не буде виконана, але так дратує політику з нульовим попередженням
Fonix

1
Бачити лише попередження, коли я використовую == 0замість цього != 0. Використання цього, як написано вище, навіть із elseблоком після, не створює жодних попереджень у Swift 4 Xcode Version 9.2 (9C40b)
shim

Також я перевірив його на цілі симулятора, а також на фізичному пристрої. Також, схоже, те саме в Swift 3.2 (та сама версія Xcode).
шим

У Xcode 9.3 + Swift 4.1 я щойно помітив, що він має попередження навіть при! = 0. Шиш.
шим

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 - Прапори клієнтів", а потім встановіть прапор у "Other Swift Flags". Ви можете встановити прапор для певної платформи, натиснувши на значок "плюс", коли наведіть курсор на конфігурацію збірки.

Є кілька переваг зробити це таким чином: 1) Ви можете використовувати той самий умовний тест ("#if TARGET_IPHONE_SIMULATOR") у вашому коді Swift та Objective-C. 2) Ви можете скласти змінні, які стосуються лише кожної збірки.

Скріншот налаштувань збірки Xcode



1

Я використовував цей код нижче в Swift 3

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

1
Я роблю це, але отримую попередження в іншому пункті, оскільки воно "ніколи не буде виконано". У нас є правила нульового попередження, так що grrrr ....
EricS

Він відображатиме попередження всякий раз, коли ви намагаєтеся запустити з пристроєм, якщо для обраного тренажера вибрано, воно не відображатиме попередження.
ak_ninan

1
застаріло
rcmstark

1

Швидкий 4:

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

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

Але, як ви знаєте, simModelCodeце не зручний код, щоб зрозуміти, який саме тренажер був запущений, тож, якщо вам потрібно, ви можете спробувати побачити цю іншу відповідь, щоб визначити поточну модель 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

Xcode 11, Swift 5

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

0

Окрім інших відповідей.

У Objective-c просто переконайтеся, що ви включили TargetConditionss .

#include <TargetConditionals.h>

перед використанням TARGET_OS_SIMULATOR.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.