Jak wykryć, czy aplikacja Swift buduje aplikację na urządzenie lub symulator

277

W Objective-C możemy wiedzieć, czy aplikacja jest budowana na urządzenie lub symulator przy użyciu makr:

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

Są to makra czasu kompilacji i niedostępne w czasie wykonywania.

Jak mogę to samo osiągnąć w Swift?

RaffAl
źródło
2
To nie jest sposób wykrywania symulatora lub rzeczywistego urządzenia w czasie wykonywania w Objective-C. Są to dyrektywy kompilatora, których wynikiem jest inny kod w zależności od kompilacji.
rmaddy
Dzięki. Zredagowałem swoje pytanie.
RaffAl
9
NAJWYŻSZE GŁOSOWANE ODPOWIEDZI NIE TO NAJLEPSZY SPOSÓB NA ROZWIĄZANIE TEGO PROBLEMU! Odpowiedź Mbelsky'ego (obecnie bardzo nisko) jest jedynym rozwiązaniem, które przychodzi bez żadnych pułapek. Nawet Greg Parker z Apple zasugerował, aby zrobić to w ten sposób: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/...
jan.vogt
1
NAWET W KAPSU, NAUCZYCIEL SUGERUJE, ŻE WSZYSTKO JEST NIEPRAWIDŁOWE Z KONTROLĄ RUNTIME. Sugestie inżynierów Apple są często źle przemyślanymi śmieciami lub mają zastosowanie tylko w niektórych sytuacjach, więc sam się znaczy mniej niż nic.
Fattie
1
@Fattie: Interesujące byłoby wiedzieć, dlaczego żadna z podanych odpowiedzi nie spełnia twoich potrzeb i czego dokładnie oczekujesz, oferując nagrodę.
Martin R

Odpowiedzi:

363

Aktualizacja 30/01/19

Chociaż ta odpowiedź może zadziałać, zalecanym rozwiązaniem dla kontroli statycznej (wyjaśnione przez kilku inżynierów Apple) jest zdefiniowanie niestandardowej flagi kompilatora ukierunkowanej na symulatory iOS. Aby uzyskać szczegółowe instrukcje, jak to zrobić, zobacz odpowiedź @ mbelsky'ego .

Oryginalna odpowiedź

Jeśli potrzebujesz sprawdzenia statycznego (np. Nie środowiska wykonawczego, jeśli / else), nie możesz bezpośrednio wykryć symulatora, ale możesz wykryć iOS na architekturze pulpitu, jak poniżej

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

Po wersji Swift 4.1

Najnowsze zastosowanie, teraz bezpośrednio dla wszystkich w jednym stanie dla wszystkich typów symulatorów, musi mieć zastosowanie tylko jeden warunek -

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

Aby uzyskać więcej wyjaśnień, możesz sprawdzić propozycję Swift SE-0190


W przypadku starszej wersji -

Oczywiście jest to fałsz na urządzeniu, ale zwraca wartość true dla symulatora iOS, jak określono w dokumentacji :

Konfiguracja kompilacji arch (i386) zwraca wartość true, gdy kod jest kompilowany dla 32-bitowego symulatora systemu iOS.

Jeśli opracowujesz symulator inny niż iOS, możesz po prostu zmienić osparametr: np

Wykryj symulator watchOS

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

Wykryj symulator tvOS

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

Lub nawet wykryj dowolny symulator

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

Jeśli zamiast tego masz rację sprawdzania czasu wykonywania, możesz sprawdzić TARGET_OS_SIMULATORzmienną (lub TARGET_IPHONE_SIMULATORw iOS 8 i niższych), co jest prawdą w symulatorze.

Zauważ, że jest to inne i nieco bardziej ograniczone niż użycie flagi preprocesora. Na przykład nie będziesz mógł używać go w miejscu, w którym a if/elsejest składniowo niepoprawny (np. Poza zakresem funkcji).

Powiedz na przykład, że chcesz mieć różne importy na urządzeniu i na symulatorze. Jest to niemożliwe przy kontroli dynamicznej, podczas gdy jest trywialne przy kontroli statycznej.

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

Ponadto, ponieważ flaga jest zastąpiona przez a 0lub a 1przez szybki preprocesor, jeśli używasz go bezpośrednio wif/else wyrażeniu, kompilator wyświetli ostrzeżenie o nieosiągalnym kodzie.

Aby obejść to ostrzeżenie, zobacz jedną z pozostałych odpowiedzi.

Gabriele Petronella
źródło
1
Więcej lektur tutaj . Aby być jeszcze bardziej restrykcyjnym, możesz użyć arch(i386) && os(iOS).
ahruss
1
To nie działało dla mnie. Musiałem sprawdzić zarówno i386, jak i x86_64
akaru
3
Ta odpowiedź nie jest najlepszym sposobem na rozwiązanie tego problemu! Odpowiedź Mbelsky'ego (obecnie bardzo nisko) jest jedynym rozwiązaniem, które przychodzi bez żadnych pułapek. Nawet Greg Parker z Apple zasugerował, aby zrobić to w ten sposób: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/...
jan.vogt
2
@ russbishop okazało się, jak dotąd, pomocną radą dla setek ludzi, kompensującą brak API. Zamiast przejmować odpowiedź, podpisując komentarz na górze, po prostu się komunikuj. Zaktualizowałem odpowiedź, aby wyjaśnić, że nie jest to już aktualne rozwiązanie i podałem link do tego, który wygląda bardziej poprawnie.
Gabriele Petronella
9
W Swift 4.1 będziesz mógł powiedzieć #if targetEnvironment(simulator):) ( github.com/apple/swift-evolution/blob/master/propozycje/... )
Hamish
172

NIEAKTUALIZOWANE DLA SWIFT 4.1. Użyj #if targetEnvironment(simulator)zamiast tego. Źródło

Aby wykryć symulator w Swift, możesz użyć konfiguracji kompilacji:

  • Zdefiniuj tę konfigurację -D IOS_SIMULATOR w Swift Compiler - Flagi niestandardowe> Inne flagi Swift
  • W tym menu wybierz Dowolny zestaw SDK symulatora systemu iOSLista rozwijana

Teraz możesz użyć tej instrukcji do wykrycia symulatora:

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

Możesz także rozszerzyć klasę UIDevice:

extension UIDevice {
    var isSimulator: Bool {
        #if IOS_SIMULATOR
            return true
        #else
            return false
        #endif
    }
}
// Example of usage: UIDevice.current.isSimulator
Mbelsky
źródło
8
To powinna być najlepsza odpowiedź! Nawet Greg Parker z Apple zasugerował w ten sposób: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/...
jan.vogt
1
aktualizacja użytkowania dla swift 3: UIDevice.current.isSimulator
tylernol
1
Czy mogę zapytać dlaczego, jeśli dodam to w części Wydanie, to nie działa?
William Hu
3
To jedyna poprawna odpowiedź. Możesz to również ustawić w xcconfigplikach, używając OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDi OTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATORby zastąpić Symulator.
Russbishop
1
W przypadku Xcode 9.2 odpowiedź nie była przez pewien czas kompilowana. Usunięcie „-” przed „D” rozwiązało problem.
Blake,
160

Zaktualizowano informacje na dzień 20 lutego 2018 r

Wygląda na to, że @ russbishop ma autorytatywną odpowiedź, która czyni tę odpowiedź „niepoprawną” - mimo że wydawała się działać przez długi czas.

Wykryj, czy aplikacja Swift tworzy aplikację na urządzenie lub symulator

Poprzednia odpowiedź

Na podstawie odpowiedzi @ WZW i komentarzy @ Panga stworzyłem prostą strukturę narzędzia. To rozwiązanie pozwala uniknąć ostrzeżeń generowanych przez odpowiedź @ WZW.

import Foundation

struct Platform {

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

}

Przykładowe użycie:

if Platform.isSimulator {
    print("Running on Simulator")
}
Daniel
źródło
10
Znacznie lepsze rozwiązanie niż zaakceptowane. Rzeczywiście, jeśli pewnego dnia (choć jest to bardzo mało prawdopodobne) Apple zdecyduje się na użycie i386 lub x85_64 na urządzeniach z iOS, zaakceptowana odpowiedź nie zadziała… lub nawet jeśli komputery stacjonarne otrzymają nowy procesor!
Frizlab
2
Potwierdzono, że działa to doskonale na Xcode 7: public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)... to samo, uproszczone. +1 dzięki
Dan Rosenstark
1
@ Daniel To działa dobrze i jest w rzeczywistości prostsze niż moje rozwiązanie. Warto jednak zauważyć, że jest bardziej ograniczony niż faktyczny krok preprocesora. Jeśli potrzebujesz, aby część kodu nie została uwzględniona w celu (np. Chcesz wybrać między dwoma importami podczas kompilacji), musisz użyć kontroli statycznej. Zredagowałem swoją odpowiedź, aby podkreślić tę różnicę.
Gabriele Petronella
Ta odpowiedź nie jest najlepszym sposobem na rozwiązanie tego problemu! Odpowiedź Mbelsky'ego (obecnie bardzo nisko) jest jedynym rozwiązaniem, które przychodzi bez żadnych pułapek. Nawet Greg Parker z Apple zasugerował, aby zrobić to w ten sposób: lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/...
jan.vogt
2
@Fattie TARGET_OS_SIMULATOR != 0jest już w odpowiedzi . To rozwiązanie podane przez Daniela. Nie ma potrzeby dodawania go ponownie w wolnej zmiennej, już tam jest. Jeśli uważasz, że posiadanie go w strukturze jest złe, a posiadanie go w wolnej zmiennej jest lepsze, opublikuj komentarz na ten temat lub stwórz własną odpowiedź. Dzięki.
Eric Aya,
69

Z Xcode 9.3

#if targetEnvironment(simulator)

Swift obsługuje nowy docelowy stan środowiska platformy z jednym ważnym symulatorem argumentów. Kompilacja warunkowa postaci „#if targetEnvironment (symulator)” może być teraz używana do wykrywania, gdy celem kompilacji jest symulator. Kompilator Swift będzie próbował wykryć, ostrzec i zasugerować użycie targetEnvironment (symulator) podczas oceny warunków platformy, które wydają się pośrednio testować środowiska symulatora, poprzez istniejące warunki platformy os () i 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
    }
}

Przed iOS 9:

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

Cel 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
źródło
2
Porównywanie ciągów jest bardziej kruche niż przy użyciu zdefiniowanych stałych.
Michael Peterson,
@ P1X3L5 masz rację! Ale zakładam, że ta metoda jest wywoływana w trybie debugowania - nie może być tak solidna, ale można ją szybko dodać do projektu
HotJard
1
@GantMan dzięki za odpowiedź. Naprawiłem kod
HotJard
@HotJard fajnie, ten nie generuje will never be executedostrzeżenia
Dannie P
59

Szybki 4

Możesz teraz użyć targetEnvironment(simulator)jako argumentu.

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

Zaktualizowano dla Xcode 9.3

Matt Swift
źródło
8
To powinna być teraz zaakceptowana odpowiedź. Chciałbym, aby istniał sposób na SO zaproponowanie nowej sugerowanej odpowiedzi w oparciu o aktualizacje systemów operacyjnych / języków programowania.
cichy
4
to świetny punkt @ quemeful - to jedna z niewielu podstawowych wad SO. Ponieważ systemy komputerowe zmieniają się tak szybko, prawie każda odpowiedź na SO staje się z czasem błędna .
Fattie
40

Pozwól mi wyjaśnić kilka rzeczy tutaj:

  1. TARGET_OS_SIMULATORw wielu przypadkach nie jest ustawiony w kodzie Swift; możesz przypadkowo go zaimportować z powodu mostkowego nagłówka, ale jest on łamliwy i nie jest obsługiwany. Nie jest to nawet możliwe w ramach. To dlatego niektórzy ludzie są zdezorientowani, czy to działa w Swift.
  2. Zdecydowanie odradzam używanie architektury jako zamiennika symulatora.

Aby wykonać kontrole dynamiczne:

Sprawdzanie ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != niljest w porządku.

Można również uzyskać symulację modelu bazowego, sprawdzając, SIMULATOR_MODEL_IDENTIFIERktóre zwracają łańcuchy takie jak iPhone10,3.

Aby wykonać kontrolę statyczną:

Xcode 9.2 i wcześniejsze: zdefiniuj własną flagę kompilacji Swift (jak pokazano w innych odpowiedziach).

Xcode 9.3+ korzysta z nowego celu Warunek środowiskowy:

#if targetEnvironment(simulator)
    // for sim only
#else
    // for device
#endif
Russbishop
źródło
1
Wygląda na to, że masz tutaj nowe informacje wewnętrzne. Bardzo pomocne! Uwaga TARGET_OS_SIMULATOR działał przez dłuższy czas zarówno w kodzie aplikacji, jak i kodzie frameworka; i działa również w Xcode 9.3 b3. Ale myślę, że to „przypadkowe”. Rodzaj śmieciarza; ponieważ wydaje się to najmniej hacking sposób. Jako dostawca kodu frameworka, który można skompilować w Xcode 9.3 lub wcześniejszym, wygląda na to, że będziemy musieli owinąć #if targetEnvironment ... w makrze #if swift (> = 4.1), aby uniknąć błędów kompilatora. Albo chyba używam .... środowiska [„SIMULATOR_DEVICE_NAME”]! = Zero. Ta kontrola wydaje się bardziej hakerska, IMO.
Daniel
jeśli wystąpił błąd „Nieoczekiwany stan platformy (oczekiwany„ os ”,„ arch ”lub„ szybki ”)” przy użyciu targetEnvironment (symulator)
Zaporozhchenko Oleksandr
@Aleksandr targetEnvironmentwylądował w Xcode 9.3. Potrzebujesz nowszej wersji Xcode.
Russbishop
@ russbishop dobra robota, wyjaśnienie tego w najnowszej nowej erze - dzięki!
Fattie
Wysłałem nagrodę w wysokości 250, ponieważ wydaje się, że ta odpowiedź zawiera najwięcej i najnowsze informacje - na zdrowie
Fattie
15

Co działa dla mnie, odkąd Swift 1.0 sprawdza architekturę inną niż arm:

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

     //simulator
#else 
     //device

#endif
akaru
źródło
14

Środowisko wykonawcze, ale prostsze niż większość innych rozwiązań tutaj:

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

Alternatywnie możesz po prostu wywołać funkcję pomocniczą Objective-C, która zwraca wartość logiczną, która używa makra preprocesora (szczególnie jeśli już miksujesz w swoim projekcie).

Edycja: Nie najlepsze rozwiązanie, szczególnie od Xcode 9.3. Zobacz odpowiedź HotJard

Podkładka
źródło
3
Robię to, ale otrzymuję ostrzeżenia w klauzuli else, ponieważ „nigdy nie zostanie ona wykonana”. Mamy zasadę zerowego ostrzeżenia, więc :-(
EricS
wyświetli ostrzeżenie, ale ma sens, w zależności od tego, czy do budowy wybrano symulator lub urządzenie, ostrzeżenie wyświetli część, która nie zostanie wykonana, ale tak irytujące zasady zerowego ostrzeżenia
Fonix
1
Widzę tylko ostrzeżenia, gdy używam == 0zamiast != 0. Użyciem tak jak powyżej, nawet w przypadku elsebloku po, nie daje ostrzeżenia w Swift 4 Xcode wersja 9.2 (9C40b)
podkładek
Testowałem go również na symulatorze, a także na urządzeniu fizycznym. Wydaje się być taki sam w Swift 3.2 (ta sama wersja Xcode).
shim
W Xcode 9.3 + Swift 4.1 właśnie zauważyłem, że ma ostrzeżenie nawet z! = 0. Do licha.
shim
10

W nowoczesnych systemach:

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

To proste.

Fattie
źródło
1
Nie jestem pewien, dlaczego pierwszy powinien być „bardziej poprawny” niż odpowiedź Daniela . - Pamiętaj, że drugi to kontrola czasu kompilacji. Szczęśliwego Nowego Roku!
Martin R
5

TARGET_IPHONE_SIMULATORjest przestarzałe w iOS 9. TARGET_OS_SIMULATORjest zamiennikiem. TARGET_OS_EMBEDDEDJest również dostępny.

From 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 */ 
Kowalik
źródło
1
próbowałem TARGET_OS_SIMULATOR, ale nie działa lub nie rozpoznaje Xcode, podczas gdy TARGET_IPHONE_SIMULATOR. Buduję dla iOS 8.0 powyżej.
CodeOverRide
Patrzę na nagłówki iOS 9. Zaktualizuję moją odpowiedź.
Kowalik
5

Mam nadzieję, że to rozszerzenie się przyda.

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

Stosowanie:

if UIDevice.isSimulator {
    print("running on simulator")
}
Lucas Chwe
źródło
@ChetanKoli, chciałem, aby kod był bardzo jasny, a nie krótki, aby był łatwy do zrozumienia dla każdego. Nie jestem pewien, co sądzę o Twojej edycji.
Lucas Chwe,
3

W Xcode 7.2 (i wcześniejszych, ale wcześniej nie testowałem), możesz ustawić flagę kompilacji specyficzną dla platformy „-D TARGET_IPHONE_SIMULATOR” dla „Any iOS Simulator”.

Sprawdź ustawienia kompilacji projektu w „Kompilatorze Swift - Flagi klienta”, a następnie ustaw flagę w „Innych flagach Swift”. Możesz ustawić flagę specyficzną dla platformy, klikając ikonę „plus” po najechaniu kursorem na konfigurację kompilacji.

Jest kilka zalet robienia tego w ten sposób: 1) Możesz użyć tego samego testu warunkowego („#if TARGET_IPHONE_SIMULATOR”) w kodzie Swift i Objective-C. 2) Możesz skompilować zmienne, które dotyczą tylko każdej kompilacji.

Zrzut ekranu ustawień kompilacji Xcode

xgerrit
źródło
1

Użyłem tego kodu poniżej w Swift 3

if TARGET_IPHONE_SIMULATOR == 1 {
    //simulator
} else {
    //device
}
ak_ninan
źródło
1
Robię to, ale otrzymuję ostrzeżenia w klauzuli else, ponieważ „nigdy nie zostanie ona wykonana”. Mamy zasadę zerowego ostrzeżenia, więc
grrrr
Będzie wyświetlał ostrzeżenie za każdym razem, gdy próbujesz uruchomić urządzenie, jeśli wybierzesz symulator do uruchomienia, nie wyświetli ostrzeżenia.
ak_ninan
1
jest przestarzałe
rcmstark
1

Swift 4:

Obecnie wolę używać klasy ProcessInfo, aby wiedzieć, czy urządzenie jest symulatorem i jakiego rodzaju urządzenia jest używane:

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

Ale, jak wiesz, simModelCodenie jest wygodnym kodem do natychmiastowego zrozumienia, który rodzaj symulatora został uruchomiony, więc jeśli potrzebujesz, możesz spróbować zobaczyć inną odpowiedź SO, aby określić aktualny model iPhone / urządzenia i mieć bardziej ludzki charakter czytelny ciąg.

Alessandro Ornano
źródło
1

Oto przykład Xcode 11 Swift oparty na świetnej odpowiedzi HotJarda powyżej , to także dodaje isDeviceBool i używa SIMULATOR_UDIDzamiast nazwy. W każdym wierszu wykonywane są zmienne przypisania, dzięki czemu można łatwiej sprawdzić je w debuggerze, jeśli zdecydujesz.

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

Jest też wpis słownikowy, DTPlatformNamektóry powinien zawierać simulator.

Alex Zavatone
źródło
0

Użyj tego kodu poniżej:

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

Działa dla Swift 4iXcode 9.4.1

Haroldo Gondim
źródło
0

Xcode 11, Swift 5

    #if !targetEnvironment(macCatalyst)
    #if targetEnvironment(simulator)
        true
    #else
        false        
    #endif
    #endif
UnchartedWorks
źródło
0

Oprócz innych odpowiedzi.

W Objective-c, upewnij się, że podałeś TargetConditionals .

#include <TargetConditionals.h>

przed użyciem TARGET_OS_SIMULATOR.

M. Ali
źródło