Avatar ·

Как программно определить, запущено ли приложение в симуляторе iPhone?

Всем привет! Я начинающий iOS-разработчик, и столкнулся с такой проблемой: мне нужно, чтобы в симуляторе выполнялся один код, а на реальном устройстве — другой. Например, я хочу использовать камеру только на реальном iPhone, а в симуляторе просто показывать заглушку. Но как это проверить программно? Я пробовал искать в интернете, но натыкался на разные макросы и флаги компиляции. В Objective-C это выглядит так: ```objectivec #if TARGET_IPHONE_SIMULATOR // код для симулятора #else // код для реального устройства #endif ``` Но я пишу на Swift, и у меня не получается напрямую перенести эту конструкцию. Я пробовал использовать `#if targetEnvironment(simulator)`, но Xcode ругается, что это недопустимая директива. Может, нужно что-то импортировать или подключать? Или есть другой способ? Также я слышал, что можно проверять архитектуру процессора (x86_64 для симулятора и arm64 для реального устройства), но это кажется ненадёжным — вдруг Apple выпустит симулятор на ARM? Подскажите, как правильно и надёжно определить симулятор в Swift-проекте. Буду очень благодарен за примеры кода! Использую Xcode 14 и Swift 5.8. <<>>

Проверка симулятора в Swift: правильный способ

Привет! Ты абсолютно прав, что конструкция из Objective-C не работает напрямую в Swift. Но в Swift есть своя, более изящная и безопасная директива компиляции. Вот как это делается:

#if targetEnvironment(simulator)
    // Этот код выполняется ТОЛЬКО в симуляторе
    print("Запущено в симуляторе iOS")
#else
    // Этот код выполняется ТОЛЬКО на реальном устройстве
    print("Запущено на реальном iPhone/iPad")
#endif

Подробное объяснение

Директива targetEnvironment(simulator) — это встроенный флаг компиляции Swift, который проверяет, какая среда выполнения указана в схеме сборки. Она работает на этапе компиляции, поэтому код внутри блоков #if и #else даже не компилируется для противоположной платформы. Это гарантирует, что:

  • Код для симулятора не попадёт в бинарник для App Store.
  • Код для реального устройства не вызовет ошибок компиляции в симуляторе (например, использование фреймворков, недоступных в симуляторе).
  • Проверка работает для всех типов симуляторов: iPhone, iPad, Apple Watch, Apple TV и т.д.

Практический пример с камерой

Допустим, ты хочешь использовать AVFoundation для работы с камерой. На реальном устройстве это работает, а в симуляторе — нет. Вот как это обернуть:

import UIKit
import AVFoundation

class CameraViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        #if targetEnvironment(simulator)
            // В симуляторе показываем заглушку
            showCameraPlaceholder()
        #else
            // На реальном устройстве инициируем камеру
            setupCameraSession()
        #endif
    }
    
    private func showCameraPlaceholder() {
        let label = UILabel()
        label.text = "Камера недоступна в симуляторе"
        label.textAlignment = .center
        view.addSubview(label)
    }
    
    private func setupCameraSession() {
        // Здесь код для AVCaptureSession
        let captureSession = AVCaptureSession()
        // ... настройка камеры
    }
}

Важные замечания

  • Не используй проверки времени выполнения вроде UIDevice.current.model или проверки архитектуры. Они могут работать ненадёжно и не защищают от случайного включения кода для симулятора в релизную сборку.
  • Директива работает на уровне компиляции, поэтому переменные, объявленные внутри блока #if, не видны снаружи. Если нужно использовать общие переменные, объявляй их до директивы.
  • Можно комбинировать с другими флагами. Например, #if targetEnvironment(simulator) && DEBUG — только для симулятора в отладочной сборке.

Что делать, если нужно проверить симулятор в runtime?

Иногда нужно проверить симулятор не на этапе компиляции, а во время выполнения (например, в библиотеке, которая компилируется заранее). Для этого используй такой трюк:

import Foundation

struct Platform {
    static let isSimulator: Bool = {
        var isSim = false
        #if targetEnvironment(simulator)
            isSim = true
        #endif
        return isSim
    }()
}

// Использование:
if Platform.isSimulator {
    print("Мы в симуляторе (runtime проверка)")
}

Этот подход создаёт вычисляемое свойство, которое инициализируется один раз при запуске приложения. Оно безопасно для использования в любом месте кода.

<<>>

Альтернативный подход: проверка через ProcessInfo

Если ты не хочешь использовать препроцессорные директивы (например, в случае, когда код поставляется как фреймворк и компилируется отдельно), можно воспользоваться проверкой переменных окружения. Симулятор всегда запускается с определёнными флагами:

import Foundation

func isRunningInSimulator() -> Bool {
    return ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil
}

// Использование
if isRunningInSimulator() {
    print("Обнаружен симулятор через переменные окружения")
}

Плюсы и минусы

  • Плюс: Работает в runtime, не требует перекомпиляции для разных платформ. Полезно для библиотек или кода, который не контролирует процесс сборки.
  • Минус: Менее надёжно, чем compile-time проверка. Apple может изменить названия переменных окружения в будущих версиях Xcode. Кроме того, эта проверка не защищает от случайного включения кода для симулятора в релизную сборку.

Когда использовать

Этот метод стоит применять только как запасной вариант, когда нет доступа к флагам компиляции. В обычной разработке iOS-приложений всегда предпочитай #if targetEnvironment(simulator) — это стандарт индустрии.