Как определить, собирается ли приложение для симулятора или реального устройства в Swift
Правильный способ: директива targetEnvironment(simulator)
Вы уже на верном пути! Действительно, в Swift для определения симулятора используется директива условной компиляции #if targetEnvironment(simulator). Она появилась в Swift 4.1 и является единственным официально рекомендуемым Apple способом для этой задачи. Ваш код почти правильный, но давайте разберём его детально и покажем лучшие практики.
Почему это работает?
Директива targetEnvironment(simulator) проверяется на этапе компиляции. Если сборка предназначена для симулятора, компилятор включает блок кода внутри #if. В противном случае (для реального устройства) выполняется блок #else. Это чисто компиляторная проверка — она не влияет на производительность во время выполнения.
Полный пример с пояснениями
import UIKit
import AVFoundation
class CameraViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Проверка на симулятор на этапе компиляции
#if targetEnvironment(simulator)
print("⚠️ Приложение запущено на симуляторе")
showSimulatorPlaceholder()
#else
// Код для реального устройства
print("✅ Приложение запущено на устройстве")
checkCameraPermissionAndOpen()
#endif
}
private func showSimulatorPlaceholder() {
let label = UILabel()
label.text = "Камера недоступна в симуляторе"
label.textAlignment = .center
label.textColor = .gray
label.frame = view.bounds
view.addSubview(label)
}
private func checkCameraPermissionAndOpen() {
// Только для реального устройства
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized:
openCamera()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
if granted {
self.openCamera()
} else {
self.showPermissionDeniedAlert()
}
}
}
case .denied, .restricted:
showPermissionDeniedAlert()
@unknown default:
fatalError("Неизвестный статус авторизации")
}
}
private func openCamera() {
// Реализация открытия камеры
print("📷 Камера открыта")
}
private func showPermissionDeniedAlert() {
let alert = UIAlertController(
title: "Нет доступа к камере",
message: "Разрешите доступ в Настройках",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
Важные моменты и лучшие практики
- Не используйте
#if TARGET_OS_SIMULATOR: Это устаревшая константа из Objective-C, которая недоступна в Swift напрямую. Она может привести к ошибкам компиляции. - Не путайте с runtime-проверками: Не пытайтесь использовать
UIDevice.current.modelилиProcessInfo.processInfo.environmentдля определения симулятора — это ненадёжно и может не сработать в будущих версиях Xcode. - Создайте удобный computed property: Для многократного использования определите свойство:
extension UIDevice {
static var isSimulator: Bool {
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}
}
// Использование:
if UIDevice.isSimulator {
print("Запущено на симуляторе")
} else {
print("Запущено на устройстве")
}
Когда это действительно нужно?
- Работа с аппаратными функциями (камера, микрофон, датчики, Face ID).
- Тестирование push-уведомлений (симулятор их не поддерживает).
- Отладка специфичных для устройства функций (например, работа с Core Bluetooth).
- Автоматическое переключение между мок-данными и реальными данными.
Заключение
Ваш подход с #if targetEnvironment(simulator) абсолютно верен. Единственное, что я бы посоветовал — вынести проверку в extension или отдельную функцию, чтобы код был чище и переиспользуемым. И помните: эта директива работает только на этапе компиляции, поэтому код для симулятора не попадёт в билд для App Store.
Альтернативный подход: использование ProcessInfo для runtime-проверки
Хотя основное решение — это директива компиляции, иногда требуется проверка во время выполнения (runtime). Например, если вы загружаете стороннюю библиотеку, которая ведёт себя по-разному на симуляторе и устройстве. В таких случаях можно использовать ProcessInfo.processInfo.environment:
import Foundation
struct EnvironmentChecker {
static var isSimulator: Bool {
return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
}
static var simulatorName: String? {
return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"]
}
}
// Использование:
if EnvironmentChecker.isSimulator {
print("Запущено на симуляторе: \(EnvironmentChecker.simulatorName ?? "неизвестно")")
} else {
print("Запущено на реальном устройстве")
}
Когда это полезно?
- Если вы подключаете фреймворки, которые не компилируются для симулятора (например, некоторые обёртки над Metal или CoreNFC).
- Для логирования или отладки, когда нужно узнать точное название симулятора.
- При работе с динамическими библиотеками, которые загружаются во время выполнения.
Недостатки runtime-подхода
- Код для устройства всё равно будет загружен в память, что может вызвать краш при обращении к неподдерживаемым API.
- Переменная окружения
SIMULATOR_DEVICE_NAMEможет измениться в будущих версиях Xcode.
Поэтому для большинства сценариев предпочтительнее использовать #if targetEnvironment(simulator), а runtime-проверку применять только как дополнение в особых случаях.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги