如何在Swift中检测是否正在为设备或模拟器构建应用程序


277

在Objective-C中,我们可以知道是否正在使用宏为设备或模拟器构建应用程序:

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

这些是编译时宏,在运行时不可用。

如何在Swift中实现相同目标?


2
这不是在Objective-C运行时检测模拟器或真实设备的方法。这些是编译器指令,根据生成的不同,它们会导致代码不同。
rmaddy14年

谢谢。我编辑了问题。
RaffAl

9
投票率最高的答案不是解决此问题的最佳方法!mbelsky的答案(目前还很遥远)是唯一没有任何陷阱的解决方案。从苹果甚至格雷格·帕克建议这样做的:lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/...
jan.vogt

1
即使只是大写,也无法对运行时检查有任何疑问。苹果工程师的建议通常是思想不周全的垃圾,或者仅在某些情况下适用,因此,初始化本身比什么都没有。
Fattie

1
@Fattie:知道为什么所有给出的答案都不能满足您的需求以及您提供赏金后您究竟希望什么,这将很有趣。
Martin R

Answers:


363

更新30/01/19

尽管此答案可能有效,但是建议进行静态检查的解决方案(如几位Apple工程师所阐明的那样)是定义一个针对iOS模拟器的自定义编译器标志。有关如何执行此操作的详细说明,请参见@mbelsky的答案

原始答案

如果需要静态检查(例如不需要运行时if / else),则不能直接检测到模拟器,但是可以在如下所示的桌面体系结构上检测iOS

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

之后雨燕4.1版本

最新用途,现在直接针对一种条件下的所有类型的所有模拟器都只需要应用一种条件-

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

有关更多说明,您可以查看Swift提案SE-0190


对于旧版本 -

显然,这在设备上为false,但对于iOS Simulator,返回的结果为true,如文档中所指定:

为32位iOS模拟器编译代码时,arch(i386)构建配置将返回true。

如果您是为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,因此如果直接在a中使用它if/else表达式中则编译器将发出有关代码无法到达的警告。

为了变通解决此警告,请参阅其他答案之一。


1
在这里阅读更多。而且要更加严格,可以使用arch(i386) && os(iOS)
ahruss

1
这对我不起作用。我必须检查i386和x86_64
akaru 2014年

3
这不是解决此问题的最佳方法!mbelsky的答案(目前还很遥远)是唯一没有任何陷阱的解决方案。从苹果甚至格雷格·帕克建议这样做的:lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/...
jan.vogt

2
到目前为止,@ russbishop已被证明对数百人有用,可以弥补API的缺失。与其通过在顶部签名来劫持答案,不如交流。我更新了答案,以澄清这不再是最新的解决方案,并且提供了指向看起来更正确的解决方案的链接。
加布里埃尔·彼得罗内拉

9
在雨燕4.1,你就可以说#if targetEnvironment(simulator):)(github.com/apple/swift-evolution/blob/master/proposals/...
麦高

172

已过时SWIFT 4.1。使用#if targetEnvironment(simulator)代替。资源

要在Swift中检测模拟器,您可以使用构建配置:

  • Swift编译器-自定义标志>其他Swift标志中定义此配置-D IOS_SIMULATOR
  • 在此下拉列表中选择任何iOS模拟器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
这应该是最好的答案!甚至来自苹果公司的格雷格·帕克(Greg Parker)都建议这样做:lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/…–
jan.vogt

1
swift 3的用法更新:UIDevice.current.isSimulator
tylernol 2016年

1
请问为什么我在“ 发布”下添加此按钮不起作用?
胡William(William Hu)

3
这是唯一正确的答案。您也可以xcconfig通过使用OTHER_SWIFT_FLAGS = TARGET_OS_EMBEDDEDOTHER_SWIFT_FLAGS[sdk=embeddedsimulator*] = TARGET_OS_SIMULATOR替代模拟器在文件中进行设置。
russbishop

1
在Xcode 9.2上,此答案有时无法编译。在“ D”之前删除“-”对我来说解决了这个问题。
布雷克

160

截至2018年2月20日的更新信息

看起来@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决定在iOS设备上使用i386或x85_64,则可接受的答案将不起作用……甚至即使台式计算机获得了新的proc!
Frizlab '16

2
确认这在Xcode 7上可以正常使用:public let IS_SIMULATOR = (TARGET_OS_SIMULATOR != 0)...相同的事情,简化了。+1感谢
Dan Rosenstark '16

1
@daniel这很好用,实际上比我的解决方案更直接。但是,值得注意的是,它比实际的预处理程序步骤更受限制。如果您需要部分代码不包含在目标中(例如,您想在编译时在两个导入之间进行选择),则必须使用静态检查。我已经编辑了答案,以突出此区别。
加布里埃尔·彼得罗内拉

这不是解决此问题的最佳方法!mbelsky的答案(目前还很遥远)是唯一没有任何陷阱的解决方案。从苹果甚至格雷格·帕克建议这样做的: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编译器在评估似乎通过现有os()和arch()平台条件间接测试模拟器环境的平台条件时,将尝试检测,警告并建议使用targetEnvironment(simulator)。(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警告
丹妮P

59

斯威夫特4

您现在可以将其targetEnvironment(simulator)用作参数。

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

更新了Xcode 9.3


8
现在,这应该是公认的答案。我希望SO上有一种方法可以基于对OS /编程语言的更新来提出新的建议答案。
凌晨

4
@quemeful是一个好点-这是SO的一些基本失败之一。由于计算系统变化如此之快,所以随着时间的流逝几乎所有关于SO的答案都会出错
Fattie

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)宏中,以避免编译器错误。或者我猜用.... environment [“ SIMULATOR_DEVICE_NAME”]!=无。IMO,此检查似乎更麻烦。
丹尼尔(Daniel)

如果使用targetEnvironment(simulator)出现“意外的平台条件(预期的'os','arch'或'swift')”错误
Zaporozhchenko Oleksandr

@Aleksandr targetEnvironment登陆Xcode 9.3。您需要更新版本的Xcode。
russbishop

@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
我这样做但是在else子句中得到警告,因为它“将永远不会执行”。我们有一个零警告规则,所以:-(
EricS

它会显示一个警告,但这是有道理的,具体取决于您是否选择了要构建的模拟器或设备,该警告将显示在将不执行的零件上,但是对零警告策略很烦人
Fonix

1
当我使用== 0而不是时,只会看到警告!= 0else在Swift 4 Xcode版本9.2(9C40b)中,使用上述方法编写的代码(即使后面有一个代码块)也不会产生任何警告
shim

我还测试了它在模拟器目标以及物理设备上运行的情况。在Swift 3.2(相同的Xcode版本)中似乎也一样。
shim

在Xcode 9.3 + Swift 4.1中,我只是注意到即使!= 0,它也确实有警告。嘘。
shim

10

在现代系统中:

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

这很容易。


1
不知道为什么第一个比丹尼尔的答案 “更正确” 。–请注意,第二个编译时检查。新年快乐!
Martin R

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,但是当TARGET_IPHONE_SIMULATOR起作用时,它无法工作或被Xcode识别。我正在为以上的iOS 8.0进行构建。
CodeOverRide 2015年

我正在查看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(及更早版本中,但我还没有测试过多少),可以为“任何iOS模拟器”设置平台特定的构建标志“ -D TARGET_IPHONE_SIMULATOR”。

在“ Swift编译器-客户标志”下查看项目构建设置,然后在“其他Swift标志”中设置标志。将鼠标悬停在构建配置上时,可以通过单击“加号”图标来设置特定于平台的标志。

这样做有两个优点:1)您可以在Swift和Objective-C代码中使用相同的条件测试(“ #if TARGET_IPHONE_SIMULATOR”)。2)您可以编译出仅适用于每个构建的变量。

Xcode构建设置屏幕截图



1

我在Swift 3中使用了以下代码

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

1
我这样做但是在else子句中得到警告,因为它“将永远不会执行”。我们有一个零警告规则,所以,grrrr ....
EricS

每当您尝试使用设备运行时,它都会显示警告,如果您选择要运行的模拟器,则不会显示警告。
ak_ninan

1
不推荐使用
rcmstark '17

1

斯威夫特4:

当前,我更喜欢使用ProcessInfo类来了解该设备是否是模拟器以及正在使用哪种设备:

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

但是,如您所知,simModelCode要立即了解启动哪种模拟器并不是一个舒适的代码,因此,如果需要,您可以尝试查看其他SO 答案,以确定当前的iPhone /设备型号并拥有更人性化的功能。可读的字符串。


1

这是一个基于HotJard 上面令人敬畏的答案的Xcode 11 Swift示例,它还添加了一个isDeviceBool而SIMULATOR_UDID不是name。变量分配在每一行上完成,因此您可以根据需要在调试器中更轻松地检查它们。

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 4Xcode 9.4.1


0

Xcode 11,Swift 5

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

0

除了其他答案。

在Objective-c中,只需确保包含TargetConditionals即可

#include <TargetConditionals.h>

使用前TARGET_OS_SIMULATOR

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.